Search code examples
androidkotlinandroidxandroid-jetpackandroid-jetpack-compose

Linkify with Compose Text


I can't find how to linkify my Text() using Jetpack Compose.

Before compose all I had to do was:

Linkify.addLinks(myTextView, Linkify.EMAIL_ADDRESSES or Linkify.WEB_URLS)

And all the links contained in my TextView were becoming clickable links, obviously.

Important: The content of the Text is coming from an API and the links do not have a fixed position and content may contain multiple links.

I want to keep this behavior with using Jetpack Compose but I can't find any information about doing that.

Does anyone know?


Solution

  • In case someone is looking for a solution, the following will make any links clickable and styled in your text:

    @Composable
    fun LinkifyText(text: String, modifier: Modifier = Modifier) {
        val uriHandler = LocalUriHandler.current
        val layoutResult = remember {
            mutableStateOf<TextLayoutResult?>(null)
        }
        val linksList = extractUrls(text)
        val annotatedString = buildAnnotatedString {
            append(text)
            linksList.forEach {
                addStyle(
                    style = SpanStyle(
                        color = Color.Companion.Blue,
                        textDecoration = TextDecoration.Underline
                    ),
                    start = it.start,
                    end = it.end
                )
                addStringAnnotation(
                    tag = "URL",
                    annotation = it.url,
                    start = it.start,
                    end = it.end
                )
            }
        }
        Text(
            text = annotatedString, 
            style = MaterialTheme.typography.body1,
            onTextLayout = { layoutResult.value = it },
            modifier = modifier
                .pointerInput(Unit) {
                    detectTapGestures { offsetPosition ->
                        layoutResult.value?.let {
                            val position = it.getOffsetForPosition(offsetPosition)
                            annotatedString.getStringAnnotations(position, position).firstOrNull()
                                ?.let { result ->
                                    if (result.tag == "URL") {
                                        uriHandler.openUri(result.item)
                                    }
                                }
                        }
                    }
                }
        )
    }
    
    private val urlPattern: Pattern = Pattern.compile(
            "(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)"
                    + "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*"
                    + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)",
            Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL
    )
    
    fun extractUrls(text: String): List<LinkInfos> {
        val matcher = urlPattern.matcher(text)
        var matchStart: Int
        var matchEnd: Int
        val links = arrayListOf<LinkInfos>()
    
        while (matcher.find()) {
            matchStart = matcher.start(1)
            matchEnd = matcher.end()
    
            var url = text.substring(matchStart, matchEnd)
            if (!url.startsWith("http://") && !url.startsWith("https://"))
                url = "https://$url"
    
            links.add(LinkInfos(url, matchStart, matchEnd))
        }
        return links
    }
    
    data class LinkInfos(
            val url: String,
            val start: Int,
            val end: Int
    )