Open1
Jetpack Compose でテーマカラーを DI したい
Jetpack Compose でテーマカラーを DI で差し替えたい場合にどのような方法がとれるか。
条件
- Theme は CompositionLocal で提供できること
- Preview がレンダリングできること
// カラーモデル
@Immutable
data class CustomColors(
val color: Color,
...
)
// CompositionLocal を準備する
// 頻繁に変更はないことを想定して static を使用する
val LocalCustomColors = staticCompositionLocalOf<CustomColors> { "error("No LocalColors provided")" }
// テーマを提供するスコープを作成する
@Composable
fun CustomTheme(
// 初期値を null にすることで Preview で楽をする
colors: CustomColors? = null,
content: @Composable () -> Unit,
) {
CompositionLocalProvider(
LocalCustomColors provides (colors ?: defaultColors),
content = content,
)
}
object CustomTheme {
val colors: CustomColors
@Composable get() = LocalCustomColors.current
}
// Composable 関数に対して DI できるように EntryPoint を定義する
@EntryPoint
@InstallIn(ActivityComponent::class)
interface DesignSystemEntryPoint {
val colors: CustomColors
}
@Suppress("unused")
@Module
@InstallIn(SingletonComponent::class)
object DesignSystemModule {
@Provides
fun provideCustomColors(): CustomColors = ... // カラーのインスタンスを提供します
}
// Context から Activity を取得する
tailrec fun Context.findActivity(): ComponentActivity? {
if (this is ComponentActivity) return this
return (this as? ContextWrapper)?.baseContext?.findActivity()
}
fun Context.requireActivity(): ComponentActivity = checkNotNull(findActivity())
Hilt の EntryPoint は Compose を使う上でも非常に強力なツールですね。
つづいて使い方。
// In Composable
val entryPoint: DesignSystemEntryPoint = remember(context) {
EntryPointAccessors.fromActivity(context.requireActivity())
}
CustomTheme(
// DI で取得したカラーが適応される
colors = designSystemEntryPoint.colors,
) {
// you can use color getting by CustomTheme.
// val color = CustomTheme.colors.color
}
// In Preview
@Preview
@Composable
fun ComponentPreview() {
// 引数を指定しなければ初期値でレンダリングされる
CustomTheme {
// you can use color getting by CustomTheme.
// val color = CustomTheme.colors.color
}
}