androidkotlinandroid-jetpack-composeandroid-jetpack-compose-text

Android Jetpack Compose: How to show styled Text from string resources


I have a string in my strings.xml which is localized for different languages. The strings are styled with Html tags for each localization.

Using Android TextView, I was able to show the styled text just fine by reading the string resources.

Considering that Jetpack Compose currently (1.0.0-rc02) does not support Html tags, I tried using TextView inside an AndroidView composable following Official Docs: https://developer.android.com/jetpack/compose/interop/interop-apis#views-in-compose

Example of what I tried:

@Composable
fun StyledText(text: String, modifier: Modifier = Modifier) {
    AndroidView(
            modifier = modifier,
            factory = { context -> TextView(context) },
            update = {
                it.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT)
            }
    )
}

The text in strings.xml file:

<string name="styled_text">Sample text with <b>bold styling</b> to test</string>

However, using stringResource(id = R.string.styled_text) provides the text without the Html tags.

Is there a way to show text from string resources with Html styles in Jetpack Compose?


The following two questions are similar, but they do not read the string from resources:

Jetpack compose display html in text

Android Compose: How to use HTML tags in a Text view


Solution

  • stringResource under the hood uses resources.getString, which discards any styled information.

    Since 1.7.0 there's a new method to parse html to AnnotatedString, AnnotatedString.fromHtml, here's how you can use it to read styled resource string:

    @Composable
    @ReadOnlyComposable
    fun annotatedStringResource(
        @StringRes id: Int,
    ): AnnotatedString {
        val text = LocalContext.current.resources.getText(id)
        val html = if (text is Spanned) {
            text.toHtml(TO_HTML_PARAGRAPH_LINES_INDIVIDUAL)
        } else {
            text.toString()
        }
        return AnnotatedString.fromHtml(html)
    }
    
    @Composable
    @ReadOnlyComposable
    fun annotatedStringResource(
        @StringRes id: Int,
        vararg formatArgs: Any,
    ): AnnotatedString {
        val text = LocalContext.current.resources.getText(id)
        val html = if (text is Spanned) {
            text.toHtml(TO_HTML_PARAGRAPH_LINES_INDIVIDUAL)
        } else {
            text.toString()
        }
        val encodedArgs = formatArgs.map { if (it is String) it.htmlEncode() else it }.toTypedArray()
        return AnnotatedString.fromHtml(html.format(*encodedArgs))
    }
    

    Note that it also has linkStyles and linkInteractionListener parameters that you may want to use to customize the result


    Alternatively, you can use TextView that will handle it for you

    @Composable
    @ReadOnlyComposable
    fun textResource(@StringRes id: Int): CharSequence =
        LocalContext.current.resources.getText(id)
    

    And use it like this:

    StyledText(textResource(id = R.string.foo))
    
    @Composable
    fun StyledText(text: CharSequence, modifier: Modifier = Modifier) {
        AndroidView(
            modifier = modifier,
            factory = { context -> TextView(context) },
            update = {
                it.text = text
            }
        )
    }