Как работает Local Composition Provider в Jetpack Compose
Что проверяют
Понимаете ли вы, как Compose неявно передаёт данные вниз по дереву и чем за это платите. Класса с названием «Local Composition Provider» в API нет, речь о связке CompositionLocal и CompositionLocalProvider. Следом обычно спрашивают, чем compositionLocalOf отличается от staticCompositionLocalOf.
Как отвечать
Механизм состоит из трёх частей:
- объявление:
val LocalUser = compositionLocalOf<User> { error("User не задан") }. Лямбда отдаёт значение по умолчанию на случай, если провайдера выше по дереву нет; - предоставление:
CompositionLocalProvider(LocalUser provides user) { Content() }. Значение действует для всего поддерева внутри лямбды, вложенный провайдер его переопределяет; - чтение:
LocalUser.currentвнутри composable-функции. Берётся значение ближайшего провайдера сверху.
Главное различие двух фабрик в отслеживании чтений. compositionLocalOf запоминает, кто прочитал current, и при смене значения рекомпозирует только их. staticCompositionLocalOf чтения не отслеживает и при смене перестраивает всё содержимое провайдера, зато само чтение дешевле. Отсюда правило: static для того, что почти не меняется (LocalContext, LocalDensity), обычная фабрика для того, что меняется часто.
На этом же механизме построены встроенные LocalContext, LocalLifecycleOwner, LocalContentColor и тема Material: MaterialTheme.colorScheme читает значение, которое положил провайдер внутри MaterialTheme(...) выше по дереву.
Где спотыкаются
Используют CompositionLocal как DI и прокидывают через него репозитории и ViewModel. Зависимость пропадает из сигнатуры, компонент нельзя переиспользовать без нужного провайдера, а превью падает на error() по умолчанию. Явные параметры почти всегда лучше.
Кладут изменяемый объект и меняют его поля. Значения сравниваются через equals, и без новой ссылки или State внутри никто не рекомпозируется.
Пытаются прочитать current в onClick или в корутине. Геттер помечен @Composable, читать его нужно в теле composable-функции и сохранять в переменную, которую затем захватит лямбда.