Lyricist - The missing I18N and I10N library for Jetpack Compose!

Overview

Maven Central Android API kotlin ktlint License MIT

Lyricist 🌎 🌍 🌏

The missing I18N and I10N library for Jetpack Compose!

Jetpack Compose greatly improved the way we build UIs on Android, but not how we interact with strings. stringResource() works well, but doesn't benefit from the idiomatic Kotlin like Compose.

Lyricist tries to make working with strings as powerful as building UIs with Compose, i.e., working with parameterized string is now typesafe, use of when expression to work with plurals with more flexibility, and even load/update the strings dynamically via an API!

Development status: Lyricist will be in Beta until Compose and KSP become stable.

Features

Limitations

  • The XML processor doesn't handle few and many plural values (PRs are welcome)

Why Lyricist?

Inspired by accompanist library: music composing is done by a composer, and since this library is about writing lyrics strings, the role of a lyricist felt like a good name.

Usage

Take a look at the sample app and sample-multi-module for working examples.

Start by declaring your strings on a data class, class or interface (pick one). The strings can be anything (really, it's up to you): Char, String, AnnotatedString, List, Set or even lambdas!

data class Strings(
    val simpleString: String,
    val annotatedString: AnnotatedString,
    val parameterString: (locale: String) -> String,
    val pluralString: (count: Int) -> String,
    val listStrings: List<String>
)

Next, create instances for each supported language and annotate with @Strings. The languageTag must be an IETF BCP47 compliant language tag (docs). You must flag one of them as default.

"Current locale: $locale" }, pluralString = { count -> val value = when (count) { 1 -> "a single apple" 2 -> "two apples" in 3..10 -> "a bunch of apples" else -> "a lot of apples" } "I have $value" }, listStrings = listOf("Avocado", "Pineapple", "Plum") ) @Strings(languageTag = Locales.PT) val PtStrings = Strings(/* pt strings */) @Strings(languageTag = Locales.ES) val EsStrings = Strings(/* es strings */) @Strings(languageTag = Locales.RU) val RuStrings = Strings(/* ru strings */) ">
@Strings(languageTag = Locales.EN, default = true)
val EnStrings = Strings(
    simpleString = "Hello Compose!",

    annotatedString = buildAnnotatedString {
        withStyle(SpanStyle(color = Color.Red)) { append("Hello ") }
        withStyle(SpanStyle(fontWeight = FontWeight.Light)) { append("Compose!") }
    },

    parameterString = { locale ->
        "Current locale: $locale"
    },

    pluralString = { count ->
        val value = when (count) {
            1 -> "a single apple"
            2 -> "two apples"
            in 3..10 -> "a bunch of apples"
            else -> "a lot of apples"
        }
        "I have $value"
    },

    listStrings = listOf("Avocado", "Pineapple", "Plum")
)

@Strings(languageTag = Locales.PT)
val PtStrings = Strings(/* pt strings */)

@Strings(languageTag = Locales.ES)
val EsStrings = Strings(/* es strings */)

@Strings(languageTag = Locales.RU)
val RuStrings = Strings(/* ru strings */)

Lyricist will generate the LocalStrings property, a CompositionLocal that provides the strings of the current locale. It will also generate rememberStrings() and ProvideStrings(), call them to make LocalStrings accessible down the tree.

val lyricist = rememberStrings()

ProvideStrings(lyricist) {
    // Content
}
Writing the code for yourself

Don't want to enable KSP to generate the code for you? No problem! Follow the steps below to integrate with Lyricist manually.

First, map each supported language tag to their corresponding instances.

val strings = mapOf(
    Locales.EN to EnStrings,
    Locales.PT to PtStrings,
    Locales.ES to EsStrings,
    Locales.RU to RuStrings
)

Next, create your LocalStrings and choose one translation as default.

val LocalStrings = staticCompositionLocalOf { EnStrings }

Finally, use the same functions, rememberStrings() and ProvideStrings(), to make your LocalStrings accessible down the tree. But this time you need to provide your strings and LocalStrings manually.

val lyricist = rememberStrings(strings)

ProvideStrings(lyricist, LocalStrings) {
    // Content
}

Now you can use LocalStrings to retrieve the current strings.

val strings = LocalStrings.current

Text(text = strings.simpleString)
// > Hello Compose!

Text(text = strings.annotatedString)
// > Hello Compose!

Text(text = strings.parameterString(lyricist.languageTag))
// > Current locale: en

Text(text = strings.pluralString(1))
Text(text = strings.pluralString(2))
Text(text = strings.pluralString(5))
Text(text = strings.pluralString(20))
// > I have a single apple
// > I have two apples
// > I have a bunch of apples
// > I have a lot of apples

Text(text = strings.listStrings.joinToString())
// > Avocado, Pineapple, Plum

Use the Lyricist instance provided by rememberStrings() to change the current locale. This will trigger a recomposition that will update the strings wherever they are being used.

lyricist.languageTag = Locales.PT

Important: Lyricist uses the System locale as default. It won't persist the current locale on storage, is outside its scope.

Multi module settings

If you are using Lyricist on a multi module project and the generated declarations (LocalStrings, rememberStrings(), ProvideStrings()) are too generic for you, provide the following (optional) arguments to KSP in the module's build.gradle.

ksp {
    arg("lyricist.packageName", "com.my.app")
    arg("lyricist.moduleName", project.name)
}

Let's say you have a "dashboard" module, the generated declarations will be LocalDashboardStrings, rememberDashboardStrings() and ProvideDashboardStrings().

Migrating from strings.xml

So you liked Lyricist, but already have a project with thousands of strings spread over multiples files? I have good news for you: Lyricist can extract these existing strings and generate all the code you just saw above.

Similar to the multi module setup, you must provide a few arguments to KSP. Lyricist will search for strings.xml files in the resources path. You can also provide a language tag to be used as default value for the LocalStrings.

ksp {
    // Required
    arg("lyricist.xml.resourcesPath", android.sourceSets.main.res.srcDirs.first().absolutePath)
    
    // Optional
    arg("lyricist.packageName", "com.my.app")
    arg("lyricist.xml.moduleName", "xml")
    arg("lyricist.xml.defaultLanguageTag", "en")
}

After the first build, the well-known rememberStrings() and ProvideStrings() (naming can vary depending on your KSP settings) will be available for use. Lyricist will also generated a Locales object containing all language tags currently in use in your project.

val lyricist = rememberStrings(strings)

ProvideStrings(lyricist, LocalStrings) {
    // Content
}

lyricist.languageTag = Locales.PT

You can easily migrate from strings.xml to Lyricist just by copying the generated files to your project. That way, you can finally say goodbye to strings.xml.

Troubleshooting

Can't use the generated code on my IDE

You should set manually the source sets of the generated files, like described here.

buildTypes {
    debug {
        sourceSets {
            main.java.srcDirs += 'build/generated/ksp/debug/kotlin/'
        }
    }
    release {
        sourceSets {
            main.java.srcDirs += 'build/generated/ksp/release/kotlin/'
        }
    }
}

Import to your project

  1. Importing the KSP plugin in the project's build.gradle then apply to your module's build.gradle
buildscript {
    dependencies {
        classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:${ksp-latest-version}"
    }
}

apply plugin: "com.google.devtools.ksp"
  1. Add the desired dependencies to your module's build.gradle
// Required
implementation "cafe.adriel.lyricist:lyricist:${latest-version}"

// If you want to use @Strings to generate code for you
compileOnly "cafe.adriel.lyricist:lyricist-processor:${latest-version}"
ksp "cafe.adriel.lyricist:lyricist-processor:${latest-version}"

// If you want to migrate from strings.xml
ksp "cafe.adriel.lyricist:lyricist-processor-xml:${latest-version}"

Current version: Maven Central

You might also like...
Compose-Ratingbar-library - A simple implementation for rating bar in Jetpack Compose

Compose-Ratingbar-library - A simple implementation for rating bar in Jetpack Compose

Compose Curved-Scroll is an Android Jetpack Compose library made with ❤️
Compose Curved-Scroll is an Android Jetpack Compose library made with ❤️

Compose-Curved-Scroll-library Compose Curved-Scroll is an Android Jetpack Compos

Android.compose.squircle - Android LightWeight Squircle Library for JetPack Compose

Android LightWeight Squircle Library for JetPack Compose Usage Based on Compose

Compose-html - An Android library which provides HTML support for Jetpack Compose texts
Compose-html - An Android library which provides HTML support for Jetpack Compose texts

HtmlCompose An Android library which provides HTML support for Jetpack Compose t

An application that i developed with a aim of learning Jetpack compose and many other jetpack libraries
An application that i developed with a aim of learning Jetpack compose and many other jetpack libraries

An application that i developed with a aim of learning Jetpack compose and many other jetpack libraries, The application make use of jikan Api which displays a list of animations,there more details and even trailers of the animations.

Jetpack Compose based project, used to stress-testing compose features / integrations and explore non-trivial functionality

Project containing Jetpack Compose samples For pagination & network images it uses CATAAS. Known issues Navigation-Compose Issue with fast tapping on

List-programminglanguage-compose - Simple implementation of a list of programming languages using LazyColumn and Coil in Jetpack Compose A Simple Blog App using Jetpack Compose, Flow, Navigation Compose, Room and Firebase
A Simple Blog App using Jetpack Compose, Flow, Navigation Compose, Room and Firebase

BlogCompose A Simple Blog App using Jetpack Compose, Flow, Navigation Compose, Room and Firebase Instructions Download your Firebase configuration fil

📱 WhatsApp clone project demonstrates modern Android development built with Jetpack Compose and Stream Chat SDK for Compose.
📱 WhatsApp clone project demonstrates modern Android development built with Jetpack Compose and Stream Chat SDK for Compose.

This is a WhatsApp clone app built with Jetpack Compose and Stream Chat SDK for Compose. The purpose of this repository is to demonstrate below: Imple

Comments
  • @LyricistStrings can't be internal

    @LyricistStrings can't be internal

    When I try to make my val annoted with @LyricistStrings internal, my app doesn't compile 😕

    error: 'public' property exposes its 'internal' type argument MyModuleToolsStrings

    opened by LincolnStuart 1
  • Bug No @Strings(default = true) found

    Bug No @Strings(default = true) found

    This error occurs during the execution of the ./gradlew test, But there is no problem when run project. Also if we not use ksp for generate code all thing be fine.

    e: Error occurred in KSP, check log for detail
    java.lang.IllegalArgumentException: No @Strings(default = true) found
    	at cafe.adriel.lyricist.processor.internal.LyricistSymbolProcessor.validate(LyricistSymbolProcessor.kt:113)
    	at cafe.adriel.lyricist.processor.internal.LyricistSymbolProcessor.finish(LyricistSymbolProcessor.kt:35)
    	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$8$1.invoke(KotlinSymbolProcessingExtension.kt:224)
    	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$8$1.invoke(KotlinSymbolProcessingExtension.kt:223)
    	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:287)
    	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:223)
    	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:120)
    	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:96)
    	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:262)
    	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:53)
    	at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:113)
    	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:253)
    	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:100)
    	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:58)
    	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
    	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:52)
    	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:92)
    	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
    	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
    	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1618)
    	at jdk.internal.reflect.GeneratedMethodAccessor106.invoke(Unknown Source)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359)
    	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
    	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
    	at java.base/java.security.AccessController.doPrivileged(Native Method)
    	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
    	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562)
    	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796)
    	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677)
    	at java.base/java.security.AccessController.doPrivileged(Native Method)
    	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676)
    	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    	at java.base/java.lang.Thread.run(Thread.java:834)
    
    opened by ghasemdev 1
  • Strings for language tag not found

    Strings for language tag not found

    Hi, I'm trying to use this library without the KSP generation and following the README for writing the code myself. However, following these instructions lead to an issue where the library doesn't fall back to the default I want if it can't find a match for the system language setting.

    Minimal example:

    package com.example.lyricisttest
    
    import android.os.Bundle
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.compose.foundation.layout.Box
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.layout.padding
    import androidx.compose.material.MaterialTheme
    import androidx.compose.material.Surface
    import androidx.compose.material.Text
    import androidx.compose.runtime.staticCompositionLocalOf
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.text.intl.Locale
    import androidx.compose.ui.unit.dp
    import androidx.compose.ui.unit.sp
    import cafe.adriel.lyricist.LyricistStrings
    import cafe.adriel.lyricist.ProvideStrings
    import cafe.adriel.lyricist.rememberStrings
    import com.example.lyricisttest.ui.theme.LyricistTestTheme
    
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                LyricistTestTheme {
                    val lyricist = rememberStrings(strings/*, Locales.ENGLISH*/)
                    ProvideStrings(lyricist, LocalStrings) {
                        Surface(
                            modifier = Modifier.fillMaxSize(),
                            color = MaterialTheme.colors.background
                        ) {
                            Box(modifier = Modifier.fillMaxSize()) {
                                Text(
                                    text = LocalStrings.current.helloWorld,
                                    modifier = Modifier.align(Alignment.Center),
                                    fontSize = 32.sp,
                                )
                                Text(
                                    text = Locale.current.toLanguageTag(),
                                    modifier = Modifier
                                        .align(Alignment.BottomCenter)
                                        .padding(bottom = 40.dp),
                                    fontSize = 24.sp,
                                )
                            }
                        }
                    }
                }
            }
        }
    }
    
    data class Strings(
        val helloWorld: String,
    )
    
    object Locales {
        const val ENGLISH = "en"
        const val SPANISH = "es"
    }
    
    @LyricistStrings(languageTag = Locales.ENGLISH, default = true)
    val english = Strings(
        helloWorld = "Hello world"
    )
    
    @LyricistStrings(languageTag = Locales.SPANISH)
    val spanish = Strings(
        helloWorld = "Hola mundo"
    )
    
    val strings = mapOf(
        Locales.ENGLISH to english,
        Locales.SPANISH to spanish,
    )
    
    val LocalStrings = staticCompositionLocalOf { english }
    

    I would expect this app to display "Hello world" if the locale is English, and "Hola mundo" if the locale is Spanish, and fallback to English if the locale is something else. As long as the locale is English or Spanish this seems to be the case:

    Screenshot_1666357623 Screenshot_1666357637 Screenshot_1666357649 Screenshot_1666357660

    All four US and UK English and Spanish combinations work as expected.

    However, if I change the system language to something else, Portuguese, instead of falling back to English the app crashes.

    2022-10-21 14:07:51.546 28520-28520/? E/AndroidRuntime: FATAL EXCEPTION: main
        Process: com.example.lyricisttest, PID: 28520
        java.lang.IllegalStateException: Strings for language tag pt-PT not found
            at cafe.adriel.lyricist.Lyricist.getStrings(Lyricist.kt:25)
            at cafe.adriel.lyricist.LyricistUtilsKt.ProvideStrings(LyricistUtils.kt:25)
            at com.example.lyricisttest.ComposableSingletons$MainActivityKt$lambda-3$1.invoke(MainActivity.kt:29)
            at com.example.lyricisttest.ComposableSingletons$MainActivityKt$lambda-3$1.invoke(MainActivity.kt:27)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
            at androidx.compose.material.MaterialTheme_androidKt.PlatformMaterialTheme(MaterialTheme.android.kt:23)
            at androidx.compose.material.MaterialThemeKt$MaterialTheme$1$1.invoke(MaterialTheme.kt:82)
            at androidx.compose.material.MaterialThemeKt$MaterialTheme$1$1.invoke(MaterialTheme.kt:81)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
            at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
            at androidx.compose.material.TextKt.ProvideTextStyle(Text.kt:265)
            at androidx.compose.material.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:81)
            at androidx.compose.material.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:80)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
            at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
            at androidx.compose.material.MaterialThemeKt.MaterialTheme(MaterialTheme.kt:72)
            at com.example.lyricisttest.ui.theme.ThemeKt.LyricistTestTheme(Theme.kt:38)
            at com.example.lyricisttest.ComposableSingletons$MainActivityKt$lambda-4$1.invoke(MainActivity.kt:27)
            at com.example.lyricisttest.ComposableSingletons$MainActivityKt$lambda-4$1.invoke(MainActivity.kt:26)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
            at androidx.compose.ui.platform.ComposeView.Content(ComposeView.android.kt:404)
            at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:250)
            at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:249)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
            at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
            at androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:177)
            at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:123)
            at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:122)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
            at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
            at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.ProvideAndroidCompositionLocals(AndroidCompositionLocals.android.kt:114)
            at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:157)
            at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:156)
    2022-10-21 14:07:51.547 28520-28520/? E/AndroidRuntime:     at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
            at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
            at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:156)
            at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:140)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
            at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
            at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:78)
            at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3248)
            at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3238)
            at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
            at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
            at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3238)
            at androidx.compose.runtime.ComposerImpl.composeContent$runtime_release(Composer.kt:3173)
            at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:587)
            at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:950)
            at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:519)
            at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:140)
            at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:131)
            at androidx.compose.ui.platform.AndroidComposeView.setOnViewTreeOwnersAvailable(AndroidComposeView.android.kt:1060)
            at androidx.compose.ui.platform.WrappedComposition.setContent(Wrapper.android.kt:131)
            at androidx.compose.ui.platform.WrappedComposition.onStateChanged(Wrapper.android.kt:182)
            at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:360)
            at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:202)
            at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:138)
            at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:131)
            at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.android.kt:1147)
            at android.view.View.dispatchAttachedToWindow(View.java:19553)
            at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3430)
            at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
            at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
            at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
            at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3437)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2028)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1721)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7598)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:966)
            at android.view.Choreographer.doCallbacks(Choreographer.java:790)
            at android.view.Choreographer.doFrame(Choreographer.java:725)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:951)
            at android.os.Handler.handleCallback(Handler.java:883)
            at android.os.Handler.dispatchMessage(Handler.java:100)
            at android.os.Looper.loop(Looper.java:214)
            at android.app.ActivityThread.main(ActivityThread.java:7356)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
    

    I can see the default argument for languageTag in rememberStrings is Locale.current.toLanguageTag(), which gets passed into the instance of Lyricist as the defaultLanguageTag. If I override that argument as in the commented code in the example above, I can force the language to English, which prevents the crash but also stops the localisation working for Spanish.

    opened by Skeletonxf 0
  • Fix recompose whole app after change languageTag

    Fix recompose whole app after change languageTag

    I used Lyricist in my project and got an issue.

    By using the code generated by KSP, after change languageTag by below way, whole app recomposed and lost current state. That is not what I want.

        val lyricist = rememberStrings()
        ProvideStrings(lyricist) {
            AppContent()
        }
        LaunchedEffect(state.languageCode) {
            if (lyricist.languageTag != state.languageCode) {
                lyricist.languageTag = state.languageCode
            }
        }
    

    As the description, I expect that it only changes wherever text, but not recompose whole compose tree.

    Use the Lyricist instance provided by rememberStrings() to change the current locale. This will trigger a recomposition that will update the strings wherever they are being used.

    That happen because you use staticCompositionLocalOf which described

    Unlike compositionLocalOf, reads of a staticCompositionLocalOf are not tracked by the composer and changing the value provided in the CompositionLocalProvider call will cause the entirety of the content to be recomposed instead of just the places where in the composition the local value is used. This lack of tracking, however, makes a staticCompositionLocalOf more efficient when the value provided is highly unlikely to or will never change.

    So I think we should move to compositionLocalOf because it provides flexible ability. I.E: change language in app settings feature.

    opened by phucynwa 0
Releases(1.2.2)
Owner
Adriel Café
Remote Android Developer
Adriel Café
A simple authentication application using Jetpack compose to illustrate signin and sign up using Mvvm, Kotlin and jetpack compose

Authentication A simple authentication application using Jetpack compose to illustrate signin and sign up using Mvvm, Kotlin and jetpack compose Scree

Felix Kariuki 5 Dec 29, 2022
Compose-navigation - Set of utils to help with integrating Jetpack Compose and Jetpack's Navigation

Jetpack Compose Navigation Set of utils to help with integrating Jetpack Compose

Adam Kobus 5 Apr 5, 2022
Jetpack Compose Boids | Flocking Insect 🐜. bird or Fish simulation using Jetpack Compose Desktop 🚀, using Canvas API 🎨

?? ?? ?? Compose flocking Ants(boids) ?? ?? ?? Jetpack compose Boids | Flocking Insect. bird or Fish simulation using Jetpack Compose Desktop ?? , usi

Chetan Gupta 38 Sep 25, 2022
A collection of animations, compositions, UIs using Jetpack Compose. You can say Jetpack Compose cookbook or play-ground if you want!

Why Not Compose! A collection of animations, compositions, UIs using Jetpack Compose. You can say Jetpack Compose cookbook or play-ground if you want!

Md. Mahmudul Hasan Shohag 186 Jan 1, 2023
Learn Jetpack Compose for Android by Examples. Learn how to use Jetpack Compose for Android App Development. Android’s modern toolkit for building native UI.

Learn Jetpack Compose for Android by Examples. Learn how to use Jetpack Compose for Android App Development. Android’s modern toolkit for building native UI.

MindOrks 382 Jan 5, 2023
This is a sample app(For beginners - App #2) built using Jetpack Compose. It demonstrates the concept of State Hoisting in Jetpack Compose.

JetBMICalculator This is a sample app(For beginners - App #2) built using Jetpack Compose. It demonstrates the concept of State Hoisting in Jetpack Co

BHAVNA THACKER 3 Dec 31, 2022
Jetpack-Compose-Demo - Instagram Profile UI using Jetpack Compose

Jetpack-Compose-Demo Instagram Profile UI using Jetpack Compose

omar 1 Aug 11, 2022
Jetpack-compose-animations-examples - Cool animations implemented with Jetpack compose

Jetpack-compose-animations-examples This repository consists of 4 animations: St

Canopas Software 180 Jan 2, 2023
Jetpack-compose-uis - A collection of some UIs using Jetpack Compose. built using Katalog

Jetpack Compose UIs This is a collection of some UIs using Jetpack Compose. It i

Mori Atsushi 3 Dec 15, 2022
A Kotlin library to use Jetpack Compose in Android and iOS. Allow to write UI for both in Kotin. Still experimental as many compose features are not yet available.

Multiplatform Compose A Kotlin library to use Jetpack Compose in Android and iOS. Allow to write UI for both in Kotin. Still experimental as many comp

Clément Beffa 548 Jan 7, 2023