Explain CompositionLocal and its use cases in Jetpack Compose?
#jetpack-compose#composition-local#dependency-injection
Answer
Overview
CompositionLocal provides implicit dependency injection down the composition tree.
Problem It Solves
kotlin// ❌ Prop drilling - passing through many layers @Composable fun App(theme: Theme) { Screen(theme) } @Composable fun Screen(theme: Theme) { Content(theme) } @Composable fun Content(theme: Theme) { Text("Hello", color = theme.textColor) }
Solution with CompositionLocal
kotlin// Define CompositionLocal val LocalTheme = compositionLocalOf<Theme> { error("No theme provided") } // Provide value @Composable fun App() { CompositionLocalProvider(LocalTheme provides DarkTheme) { Screen() // No need to pass theme } } // Consume value @Composable fun Content() { val theme = LocalTheme.current Text("Hello", color = theme.textColor) }
Built-in CompositionLocals
kotlin// Context val context = LocalContext.current // Configuration val configuration = LocalConfiguration.current // Density val density = LocalDensity.current // Text style val textStyle = LocalTextStyle.current
Creating Custom CompositionLocal
Static Value
kotlindata class AppConfig(val apiKey: String, val baseUrl: String) val LocalAppConfig = staticCompositionLocalOf { AppConfig("", "") }
Dynamic Value
kotlinval LocalUser = compositionLocalOf<User?> { null } @Composable fun UserProvider(user: User, content: @Composable () -> Unit) { CompositionLocalProvider(LocalUser provides user) { content() } }
Use Cases
1. Theme
kotlin@Composable fun MaterialTheme( colors: Colors, typography: Typography, content: @Composable () -> Unit ) { CompositionLocalProvider( LocalColors provides colors, LocalTypography provides typography ) { content() } }
2. Navigation
kotlinval LocalNavController = compositionLocalOf<NavController> { error("No NavController") } @Composable fun MyButton() { val navController = LocalNavController.current Button(onClick = { navController.navigate("details") }) { Text("Go") } }
3. Repository/ViewModel
kotlinval LocalRepository = compositionLocalOf<UserRepository> { error("No repository") } @Composable fun App() { val repository = remember { UserRepository() } CompositionLocalProvider(LocalRepository provides repository) { HomeScreen() } }
staticCompositionLocalOf vs compositionLocalOf
kotlin// Use staticCompositionLocalOf when value rarely changes val LocalAppConfig = staticCompositionLocalOf { AppConfig() } // Use compositionLocalOf when value changes frequently val LocalUser = compositionLocalOf<User?> { null }
Performance:
- : Changing value recomposes entire treetext
staticCompositionLocalOf - : Only recomposes consumerstext
compositionLocalOf
Best Practice: Use CompositionLocal sparingly. Explicit parameters are usually better.