0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Jetpack Compose でActivity再起動なしにカラーテーマを切り替える

0
Posted at

はじめに

アプリ内でダーク/ライト/システムを選べるテーマ設定を実装したところ、切り替えるたびに設定シートの下でホーム画面が一瞬ちらつきました。

原因は Activity.recreate() です。recreate でシートがいったん閉じ、新しい Activity が描画されるまでの間に背後の画面が見えていました。

この記事では、Activity を再起動せず、Compose の中でテーマを切り替える方法を残します。

テーマの定義

enum class AppColorTheme { Dark, Light, System }

fun AppColorTheme.isDark(base: Configuration): Boolean = when (this) {
    AppColorTheme.Dark -> true
    AppColorTheme.Light -> false
    AppColorTheme.System ->
        (base.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
}

解決策:AppColorThemeHost

MainActivity.kt
setContent {
    val theme by colorThemeRepository.get().collectAsStateWithLifecycle(initialTheme)
    AppColorThemeHost(activity = this, theme = theme) {
        AppTheme { AppContent() }
    }
}
AppColorThemeHost.kt
@Composable
fun AppColorThemeHost(
    activity: ComponentActivity,
    theme: AppColorTheme,
    content: @Composable () -> Unit,
) {
    val base = LocalConfiguration.current
    val baseContext = LocalContext.current
    val isDark = theme.isDark(base)

    val configuration = remember(base, isDark) {
        Configuration(base).apply {
            val night = if (isDark) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO
            uiMode = (uiMode and Configuration.UI_MODE_NIGHT_MASK.inv()) or night
        }
    }
    // createConfigurationContext() だと Activity をたどれなくなるため ContextThemeWrapper を使う
    val context = remember(baseContext, configuration) {
        ContextThemeWrapper(baseContext, R.style.Theme_YourApp).apply {
            applyOverrideConfiguration(configuration)
        }
    }

    LaunchedEffect(isDark) {
        val style = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT) { isDark }
        activity.enableEdgeToEdge(statusBarStyle = style, navigationBarStyle = style)
    }

    CompositionLocalProvider(
        LocalConfiguration provides configuration,
        LocalContext provides context,
        content = content,
    )
}

LocalConfiguration と LocalContext を上書きすると、isSystemInDarkTheme() も -night リソースもユーザーが選んだテーマに従うようになり、Activity を再起動する必要がなくなります。

ハマりポイント1:configChanges="uiMode"

これだけでは、テーマを切り替えるたびにシステムが Activity を再起動してしまいます。uiMode の変更を自分で処理すると宣言する必要があります。

AndroidManifest.xml
<activity
    android:name=".MainActivity"
    android:configChanges="uiMode" />

ハマりポイント2:ModalBottomSheet

ModalBottomSheet は別の Window で描画されるので、シートの中ではテーマがシステムの値に戻ってしまいます。シートの内側で、ホスト側の値を入れ直します。

ThemedModalBottomSheet.kt
@Composable
fun ThemedModalBottomSheet(onDismissRequest: () -> Unit, content: @Composable () -> Unit) {
    val hostConfiguration = LocalConfiguration.current
    val hostContext = LocalContext.current

    ModalBottomSheet(onDismissRequest = onDismissRequest) {
        CompositionLocalProvider(
            LocalConfiguration provides hostConfiguration,
            LocalContext provides hostContext,
        ) {
            // シートのシステムバーのアイコン色もテーマに合わせる
            val isDark = isSystemInDarkTheme()
            val view = LocalView.current
            SideEffect {
                val window = (view.parent as? DialogWindowProvider)?.window ?: return@SideEffect
                WindowCompat.getInsetsController(window, view).apply {
                    isAppearanceLightStatusBars = !isDark
                    isAppearanceLightNavigationBars = !isDark
                }
            }
            content()
        }
    }
}

おわりに

CompositionLocalProvider で Configuration を上書きすれば、ナビゲーションの状態や画面の状態を保ったままテーマを切り替えられます。configChanges と ModalBottomSheet の対応は見落としやすいので、同じ実装をする方の参考になれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?