AutoLinkText is a simple library that makes links, emails, and phone numbers clickable in text in Jetpack Compose and Kotlin Compose Multiplatform.
The latest demo app APK can be found in the releases section under the "Assets" section of the latest release.
TextView
has autoLink
and Linkify
but Compose doesn't have an equivalent. This library aims to fill that gap.
Android | iOS |
---|---|
Desktop | Web |
---|---|
- Make links, emails, and phone numbers clickable in your text out of the box
- Create custom matchers for your own patterns (e.g. hashtags, mentions, etc.)
- Customizable styling for links
- Customizable click listeners for links
- Supports Compose Multiplatform (Android, iOS, Desktop/JVM, Wasm, JS)
If you're using Version Catalog, add the following to your libs.versions.toml
file:
[versions]
#...
autolinktext = "2.0.0"
[libraries]
#...
autolinktext = { module = "sh.calvin.autolinktext:autolinktext", version.ref = "autolinktext" }
or
[libraries]
#...
autolinktext = { module = "sh.calvin.autolinktext:autolinktext", version = "2.0.0" }
If you're using Gradle instead, add the following to your build.gradle
file:
dependencies {
implementation("sh.calvin.autolinktext:autolinktext:2.0.0")
}
dependencies {
implementation 'sh.calvin.autolinktext:autolinktext:2.0.0'
}
See demo app code for more examples.
By default AutoLinkText
turns URLs, emails, and phone numbers into clickable links, set the color to MaterialTheme.colorScheme.primary
, and underline the links.
Text(
AnnotatedString.rememberAutoLinkText(
"""
|Visit https://www.google.com
|Visit www.google.com
|Email [email protected]
|Call 6045557890
|Call +1 (604) 555-7890
|Call 604-555-7890
""".trimMargin()
)
)
You can override the default styling by providing a TextLinkStyles
object.
Text(
AnnotatedString.rememberAutoLinkText(
"...",
defaultLinkStyles = TextLinkStyles(
SpanStyle(
color = Color.Blue,
textDecoration = TextDecoration.Underline
)
)
)
)
There are 3 types of TextRule
s: Url
, Clickable
, and Styleable
.
Url
turns matched text into a clickable link, and when clicked, opens the URL.Clickable
turns matched text into a clickable link, and when clicked, calls theonClick
lambda.Styleable
applies aSpanStyle
to the matched text.
Text(
AnnotatedString.rememberAutoLinkText(
"Make your own rules like #hashtag and @mention",
textRules = listOf(
TextRule.Styleable(
textMatcher = TextMatcher.StringMatcher("Make"),
style = SpanStyle(fontWeight = FontWeight.Bold)
),
TextRule.Clickable(
textMatcher = TextMatcher.RegexMatcher(Regex("#\\w+")),
onClick = {
println("Hashtag ${it.matchedText} clicked")
},
),
TextRule.Url(
textMatcher = TextMatcher.RegexMatcher(Regex("@\\w+")),
styles = TextLinkStyles(
SpanStyle(
color = Color.Blue
)
),
urlProvider = { "https://twitter.com/${it.matchedText}" }
)
)
)
)
Provide a stylesProvider
lambda that returns a TextLinkStyles
object based on the matched text.
Text(
AnnotatedString.rememberAutoLinkText(
"Style the same rule differently like #hashtag1 and #hashtag2",
textRules = listOf(
TextRule.Clickable(
textMatcher = TextMatcher.RegexMatcher(Regex("#\\w+")),
stylesProvider = {
val hashtag = it.matchedText
if (hashtag == "#hashtag1") {
TextLinkStyles(
SpanStyle(
color = Color.Red,
textDecoration = TextDecoration.Underline
)
)
} else {
TextLinkStyles(
SpanStyle(
color = Color.Blue,
textDecoration = TextDecoration.Underline
)
)
}
},
onClick = {
println("Hashtag ${it.matchedText} clicked")
},
),
)
)
)
You can create TextRule
s that are not clickable by using TextRule.Styleable
.
Text(
AnnotatedString.rememberAutoLinkText(
"This is very important",
textRules = listOf(
TextRule.Styleable(
textMatcher = TextMatcher.StringMatcher("important"),
style = SpanStyle(color = Color.Red),
)
),
)
)
Create your own matchers with TextMatcher.FunctionMatcher
that takes the given text and returns a list of SimpleTextMatchResult
s.
Text(
AnnotatedString.rememberAutoLinkText(
"Make every other word blue",
textRules = listOf(
TextRule.Styleable(
textMatcher = TextMatcher.FunctionMatcher {
val matches = mutableListOf<SimpleTextMatchResult<Nothing?>>()
var currentWordStart = 0
"$it ".forEachIndexed { index, char ->
if (char.isWhitespace()) {
val match = SimpleTextMatchResult(
start = currentWordStart,
end = index,
)
if (it.slice(match).isNotBlank()) {
matches.add(match)
}
currentWordStart = index + 1
}
}
matches.filterIndexed { index, _ -> index % 2 == 0 }
},
style = SpanStyle(color = Color.Blue),
),
),
)
)
- AutoLinkText
- TextRule
- TextRuleDefaults
- TextMatcher
- TextMatcherDefaults
- MatchFilter
- MatchFilterDefaults
- MatchStylesProvider
- SimpleTextMatchResult
- TextMatchResult
- MatchClickHandlerDefaults
To run the Android demo app, open the project in Android Studio and run the app.
To run the iOS demo app, open the iosApp project in Xcode and run the app or add the following Configuration to the Android Studio project, you may need to install the Kotlin Multiplatform Mobile plugin first.
To run the web demo app, run ./gradlew :composeApp:wasmJsBrowserDevelopmentRun
.
To run the desktop demo app, run ./gradlew :demoApp:ComposeApp:run
.
Open this project with Android Studio Preview.
You'll want to install the Kotlin Multiplatform Mobile plugin in Android Studio before you open this project.
Copyright 2023 Calvin Liang
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.