不适用于 android 13(API 30) 的 jetpack compose 的 reCreate() 活动

问题描述 投票:0回答:1

当我调用重新创建功能来更改我的应用程序主题时,

SetContent{}
功能不起作用

你好 我尝试更改使用 jetpack compose(material v1.2.0-alpha12)和 android 版本 13(API 30)开发的应用程序中的主题 但提交配置后,活动的

reCreate
功能不起作用 我尝试将 Log.i 放入我的代码中并对其进行调试,我所知道的是在
setContent{}
生命周期之后再次调用时不起作用
reCreate
有人可以帮助我吗?

android android-jetpack-compose android-jetpack material-components-android android-jetpack-compose-material3
1个回答
0
投票

经过相当长一段时间的修补后,我成功更新了

Locale
并重新创建了更改应用语言的 Activity,同时还使用新的本地化方法在 Android 13 (API 33) 上工作。

我提供了如何实现它的示例指南,包括如何在 Compose 中更改主题。

需要注意的是,我使用Preferences DataStore作为存储首选项的解决方案,而不是Shared Preferences,因为它具有许多优点(例如,无需监听器即可实时观察),并与使用DaggerHilt的依赖注入相结合.

我强烈建议使用具有针对不同屏幕的导航的单个活动,因为它更容易维护,并且应用程序不会被活动填满。这是如何实现此类导航的详细指南。

示例指南的结构:

  1. 可用应用程序语言的列表。
  2. 通过更新语言环境来设置应用程序语言的功能。
  3. 自定义主题模式方法。
  4. 使用首选项数据存储的应用程序首选项。
  5. 设置依赖注入。
  6. 包含状态和回调的 ViewModel。
  7. 活动实施。

如果您需要进一步帮助,请随时与我联系。


让我们首先创建一个包含应用程序语言的列表:

val appLanguages = listOf("en", "bg" /* More languages ... */ )  

然后,创建一个可以在

Activity
中调用的函数,其中包含设置语言所需的逻辑。我强烈建议将此函数放在一个单独的 Kotlin 文件中,这样它就不会耦合到单个
Activity
,然后以生命周期方式在
onCreate
Activity
函数中调用它(如最后一个示例所示) :

fun Activity.setAppLanguage(languageTag: String) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {

        // Change the Locale for Android version >= 13.

        val locale = getSystemService(LocaleManager::class.java).applicationLocales
        val newLocale = LocaleList(Locale.forLanguageTag(languageTag))

        if (locale != newLocale) {

            // Set the new Locale.
            getSystemService(LocaleManager::class.java).applicationLocales = newLocale

            // Recreate the Activity.
            recreate()

        }

    } else {

        // Change the Locale for Android version <= 12.

        val locale = Locale.getDefault()
        val newLocale = Locale(languageTag)

        if (locale != newLocale) {

            // Apply the new Locale to the configuration.
            val configuration = resources.configuration.apply {
                Locale.setDefault(newLocale)
                setLocale(newLocale)
            }

            // Update the configuration.
            @Suppress("Deprecation")
            resources.updateConfiguration(configuration, resources.displayMetrics)

            // Recreate the Activity.
            recreate()

        }

    }

}

可以使用以下方法更改 Compose 中的主题:

  • 自定义
    ThemeMode
    枚举类,包含必要的主题模式,例如:
    SYSTEM
    LIGHT
    DARK
  • 自定义
    LocalThemeMode
    CompositionLocal 键,用于提供和使用组合中的
    ThemeMode
  • 自定义
    isInDarkTheme()
    可组合函数,用于检查当前应用的主题是否为深色。
  • 优先存储可实时观察的
    ThemeMode
    。 (如下所示):
/** A custom enum class, containing the necessary theme modes of the app. */
enum class ThemeMode {

    @RequiresApi(Build.VERSION_CODES.P)
    SYSTEM,

    LIGHT,

    DARK;

    companion object {

        // SYSTEM theme is available on Android 9+
        val Default = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) SYSTEM else LIGHT

    }

}

/** CompositionLocal key to provide and consume the applied theme mode down the composition. */
val LocalThemeMode = staticCompositionLocalOf { ThemeMode.Default }

/** A function to check whether the currently applied [ThemeMode] is dark. */
@Composable
fun isInDarkTheme(): Boolean = when (LocalThemeMode.current) {
    ThemeMode.DARK -> true
    ThemeMode.LIGHT -> false
    else -> isSystemInDarkTheme()
}
  • 在应用程序主题可组合项内,将
    darkTheme: Boolean
    参数替换为
    themeMode: ThemeMode
    ,并使用
    LocalThemeMode
    提供
    CompositionLocalProvider
    键,并声明
    darkTheme
    属性:
@Composable
fun MyApplicationTheme(
    themeMode: ThemeMode = ThemeMode.Default,
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {

    CompositionLocalProvider(
        LocalThemeMode provides themeMode
    ) {

        val darkTheme = isInDarkTheme()
        val colorScheme = when {

            dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {

                val context = LocalContext.current

                if (darkTheme) dynamicDarkColorScheme(context)
                else dynamicLightColorScheme(context)

            }

            darkTheme -> DarkColorScheme
            else -> LightColorScheme

        }

        MaterialTheme(
            colorScheme = colorScheme,
            typography = Typography,
            content = content
        )

    }

}

创建首选项数据存储:

  • 这是一个带有应用程序首选项的界面(这只是一个带有语言和主题首选项的示例,请随意根据您的需要进行调整):
interface AppPreferences {

    // Language

    suspend fun setLanguage(language: String)
    fun getLanguage(): Flow<String?>

    // Theme

    suspend fun setTheme(theme: ThemeMode)
    fun getTheme(): Flow<ThemeMode?>

}
  • 这是
    AppPreferences
    的实现类,包含保存和读取首选项的逻辑:
class AppPreferencesImpl @Inject constructor(
    private val dataStore: DataStore<Preferences>
) : AppPreferences {

    companion object {
        private val LANGUAGE_KEY = stringPreferencesKey("language")
        private val THEME_KEY = stringPreferencesKey("theme")
    }

    // Language

    override suspend fun setLanguage(language: String) {
        dataStore.edit { it[LANGUAGE_KEY] = language }
    }

    override fun getLanguage(): Flow<String?> {
        return dataStore.data.map { it[LANGUAGE_KEY] }
    }

    // Theme

    override suspend fun setTheme(theme: ThemeMode) {
        dataStore.edit { it[THEME_KEY] = theme.name }
    }

    override fun getTheme(): Flow<ThemeMode?> {
        return dataStore.data.map {
            it[THEME_KEY]?.let { theme -> ThemeMode.valueOf(theme) }
        }
    }

}

设置依赖注入:

  • 要使用 DaggerHilt 注入依赖项(在本例中为应用程序首选项),需要有一个用
    @HiltAndroidApp
    注释的应用程序类,该类也作为
    android:name
    属性包含在应用程序清单
    application
    标记中:
@HiltAndroidApp
class App : Application()
<application
    android:name=".App">

    <!---->

</application>
  • 另一个要求是有一个模块来提供依赖项,例如
    AppPreferencesModule
    ,它提供了
    AppPreferences
    的单个实例用于注入:
@Module
@InstallIn(SingletonComponent::class)
object AppPreferencesModule {

    private val Context.dataStore by preferencesDataStore("appPreferences")

    @Provides
    @Singleton
    fun provideAppPreferences(@ApplicationContext context: Context): AppPreferences {
        return AppPreferencesImpl(context.dataStore)
    }

}

包含状态和回调的 ViewModel:

  • ViewModel 有一个

    AppPreferences
    的注入实例,该实例被转换为状态以供 UI 实时观察:

  • 更改首选项的回调可以在另一个 ViewModel 中实现(例如,设置屏幕,其中也注入了

    AppPreferences
    实例)。

@HiltViewModel
class MainActivityViewModel @Inject constructor(
    private val appPreferences: AppPreferences
) : ViewModel() {

    val languageState = appPreferences.getLanguage().filterNotNull().stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5_000L),
        initialValue = Locale.getDefault().toLanguageTag().uppercase()
    )

    val themeModeState = appPreferences.getTheme().filterNotNull().stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5_000L),
        initialValue = ThemeMode.Default
    )

    fun setLanguage(languageTag: String) {
        viewModelScope.launch {
            withContext(Dispatchers.IO) {
                appPreferences.setLanguage(languageTag)
            }
        }
    }

    fun setTheme(themeMode: ThemeMode) {
        viewModelScope.launch {
            withContext(Dispatchers.IO) {
                appPreferences.setTheme(themeMode)
            }
        }
    }

}

最后是Activity的实现:

  • 该活动用

    @AndroidEntryPoint
    进行注释,这标志着要设置用于注入的 Android 组件类。

  • setAppLanguage()
    函数以生命周期方式在collected内部执行
    languageState

  • themeModeState
    也以生命周期方式收集,并将其作为参数传递给已经适应的应用程序主题可组合项。

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    private val viewModel by viewModels<MainActivityViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Set the application language.
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.languageState.collectLatest { setAppLanguage(it) }
            }
        }

        setContent {

            val themeMode by viewModel.themeModeState.collectAsStateWithLifecycle()

            MyApplicationTheme(
                themeMode = themeMode
            ) {

                // ...
            }

        }

    }

}

这就是更新

Locale
并更改 Compose 中主题的示例方法。

如果您需要进一步帮助,请随时与我联系。

快乐编码! 🙌🏼

© www.soinside.com 2019 - 2024. All rights reserved.