CompositionLocal di Jetpack Compose. Apa itu dan bagaimana menerapkan lokalisasi reaktif dari aplikasi yang menggunakannya

Kekuatan dalam blog Technocracy jatuh ke tangan pengembang android. Vladislav Titov menjelaskan cara mencapai UI tanpa gangguan saat mengubah pelokalan.





Apa masalahnya

Bekerja dengan sumber daya string di Android diatur sebagai berikut:





  • string disimpan sebagai markup xml di folder nilai yang berbeda 





  • Untuk mendapatkan string dalam kode, Anda perlu merujuk ke objek Konteks.





Context . , , -, Locale Locale.setDefault(), -, Context Locale attachBaseContext(). , UI. ? .





Jetpack Compose , ()

, Compose โ€” .





:





@Composable
fun Hello() {
    Text(text = "Hello Compose!")
}
      
      



. , , . , . :






@Composable
fun Hello() {
    var name = "Compose"
    Text(text = "Hello $name!")
    TextField(
        value = name,
        onValueChanged = { name = it }
    )
}
      
      



TextField name, . , . , . Compose , name Text. ?





State โ€” . Compose , , . . , .





@Composable
fun Hello() {
    var name by mutableStateOf("Compose")
    Text(text = "Hello ${name}!")
    TextField(
        value = name,
        onValueChanged = { name = it }
    )
}
      
      



mutableStateOf โ€” -builder Composable. MutableState, State, property value. . State , property delegate by ( ). state.value.





(MutableState), . , , , view. , ViewModel, var name by mutableStateOf("Compose") val name by viewModel.nameLiveData.observeAsState(""), onValueChanged = { name = it } onValueChanged = { viewModel.setName(it) }. , var name val name. .





. State , Compose , Composable-. , . , , . , , . :





, Composable, State, . var name by mutableStateOf("Compose") . . , remember:






@Composable
fun Hello() {
    val name by remember { mutableStateOf("Compose") }
    Text(text = "Hello ${name}!")
    TextField(
        value = name,
        onValueChanged = { name = it }
    )
}
      
      



Composable โ€œโ€ , . , Composable-. 





, Composable side-. - Composable side- . , Composable . , , . .





, , .





CompositionLocal

, , Composable- . . . , . , MyApp , . MyApp .






class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val viewModel: MainViewModel = viewModel()
            MyApp(viewModel.user)
        }
    }
}

@Composable
fun MyApp(user: User) {
    UserWidget(user)
}

@Composable
fun UserWidget(user: User) {
    Text(text = user.name)
}
      
      



, Composable, . 





CompositionLocal. State, , Composable . CompositionLocal Composable. : โ€œ CompositionLocal side-?โ€ - . โ€” .






val ActiveUser = compositionLocalOf<User> { error("No active user found!") }

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val viewModel: MainViewModel = viewModel()
            CompositionLocalProvider(ActiveUser provides viewModel.user) {
                MyApp()
            }
        }
    }
}

@Composable
fun MyApp() {
    UserWidget()
}

@Composable
fun UserWidget() {
    val user = ActiveUser.current
    Text(text = user.name)
}
      
      



, - compositionLocalOf CompositionLocal User ActiveUser. trailing Userโ€™, , .





- Composable ( , ) CompositionLocal. CompositionLocalProvider(ActiveUser provides viewModel.user) { //contnt }. ActiveUser , , content. user. Providerโ€™. CompositionLocal side-.





CompositionLocal, Composable property CompositionLocal.current. State, Compose , CompositionLocal, Composable. setContent UserWidget, MyApp. , Composable , .





: Context

, , . LocalContext: CompositionLocal Context . , , Jetpack Compose. .





data class Localization(
    val locale: Locale, 
    val strings: MutableMap<String, String> = mutableMapOf()
)

      
      



, : , , . .





, :





  • localizationMap โ€” , O(1), O(n).





  • supportedLocales





  • , .





internal val defaultLocalization: Localization = Localization(Locale.ENGLISH)

private val supportedLocales: MutableSet<Locale> = mutableSetOf()

private val localizationMap = hashMapOf<Locale, Localization>()

fun registerSupportedLocales(vararg locales: Locale): Set<Locale> {
    locales.filter { it != Locale.ENGLISH }
        .forEach {
            if (supportedLocales.add(it)) {
                registerLocalizationForLocale(it)
            }
        }
    return supportedLocales + Locale.ENGLISH
}

private fun registerLocalizationForLocale(locale: Locale) {
    localizationMap[locale] = Localization(locale)
}
      
      



, , , . . , :





fun Translatable(name: String, defaultValue: String, localeToValue: () -> Map<Locale, String>): Localization.() -> String {
    defaultLocalization.strings[name] = defaultValue
    for ((locale, value) in localeToValue().entries) {
        val localization = localizationMap[locale] ?: throw RuntimeException("There is no locale $locale")
        localization.strings[name] = value
    }
    return fun Localization.(): String {
        return this.strings[name] ?: defaultLocalization.strings[name] ?: throw RuntimeException("There is no string called $name in localization $this")
    }
}

fun NonTranslatable(name: String, defaultValue: String): Localization.() -> String {
    defaultLocalization.strings[name] = defaultValue
    return fun Localization.(): String {
        return defaultLocalization.strings[name] ?: throw RuntimeException("There is no string called $name in localization default")
    }
}
      
      



( ):





name โ€” . . 





defaultValue  โ€” ,  





closure localeToValue, -.





: . . extension-, Localization, โ€” .





extension- . , . ? CompositionLocal:





val LocalLocalization = compositionLocalOf { defaultLocalization } 
      
      



, LocalLocalization object ( MaterialTheme):





object Vocabulary {
    @Composable
    @get:ReadOnlyComposable
    val localization: Localization get() = LocalLocalization.current
}
      
      



CompositionLocal:





@Composable
fun Localization(locale: Locale, content: @Composable () -> Unit) {
    CompositionLocalProvider(
        LocalLocalization provides (localizationMap[locale] ?: defaultLocalization),
        children = content
    )
}
      
      



1.





val RUSSIAN = Locale("ru")
val TATAR = Locale("tt")

val supportedLocalesNow = registerSupportedLocales(RUSSIAN, TATAR)
      
      



2.





val hello = Translatable("hello", "Hello!") {
    hashMapOf(
        RUSSIAN to "!",
        TATAR to "ำ™!"
    )
}
val nonTrans = NonTranslatable("format", "%1\$d:%2\$02d")
      
      



3.





class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Localization(locale = TATAR /*smth from supportedLocalesNow*/) {
                val localization = Vocabulary.localization
                Text(text = localization.hello())
                Text(text = localization.nonTrans().format(20, 9))
            }
        }
    }
}
      
      



4. !





, Localization, Activity





P.S. , .





plurals, , Translatable NonTranslatable. type-safety . . Formatter, โ€” MessageFormat, . 





, , , . !





Jetpack Compose Localization - https://github.com/TechnokratosDev/jetpack-compose-localization





Jetpack Compose - https://developer.android.com/jetpack/compose/mental-model





Composable - https://medium.com/androiddevelopers/under-the-hood-of-jetpack-compose-part-2-of-2-37b2c20c6cdd





Codelab Compose - https://codelabs.developers.google.com/codelabs/jetpack-compose-basics





ยซยป. 4:28.





Telegram- ยซ ยป, .








All Articles