← Назад к вопросам

Что такое CompositionLocal в Jetpack Compose?

2.0 Middle🔥 113 комментариев
#UI и вёрстка#Архитектура и паттерны

Комментарии (3)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое CompositionLocal?

CompositionLocal — это механизм в Jetpack Compose для неявной передачи данных через дерево композиции без необходимости явно передавать их через параметры каждой составной функции (composable). Это аналог Context в традиционном Android View-мире или Dependency Injection в других фреймворках, но адаптированный для реактивной парадигмы Compose.

Основная проблема, которую решает CompositionLocal

Представьте, что у вас есть глубокое дерево composable-функций, и корневому компоненту нужно передать данные (например, тему, конфигурацию локали, ресурсы) глубоко вложенному дочернему компоненту. Без CompositionLocal вам пришлось бы передавать эти данные через параметры всех промежуточных composable-функций, даже если они сами их не используют — это называется "prop drilling".

Пример проблемы (без CompositionLocal):

@Composable
fun App(userPreferences: UserPreferences, theme: Theme, locale: Locale) {
    MainScreen(userPreferences, theme, locale)
}

@Composable
fun MainScreen(userPreferences: UserPreferences, theme: Theme, locale: Locale) {
    // Не использует theme, но должен передать её дальше
    UserProfile(userPreferences, theme, locale)
}

@Composable 
fun UserProfile(userPreferences: UserPreferences, theme: Theme, locale: Locale) {
    // Использует только locale, но получил все параметры
    Text(text = "Hello", color = theme.primary, fontSize = locale.fontSize)
}

Как работает CompositionLocal

CompositionLocal создаёт скопированное значение, доступное всем composable-функциям вниз по дереву композиции от точки его предоставления. Он состоит из двух ключевых частей:

  1. CompositionLocal<T> — контейнер, объявляющий тип данных.
  2. Provider (CompositionLocalProvider) — компонент, который предоставляет значение для этого контейнера.

Пример создания и использования:

// 1. Создание CompositionLocal (обычно в object или верхнем уровне)
val LocalAppTheme = compositionLocalOf<Theme> { 
    error("Theme не предоставлен") 
}

val LocalLocale = staticCompositionLocalOf<Locale> { 
    Locale.getDefault()
}

// 2. Предоставление значения в корне или конкретной части дерева
@Composable
fun App() {
    CompositionLocalProvider(
        LocalAppTheme provides DarkTheme(),
        LocalLocale provides Locale("ru")
    ) {
        MainScreen() // Больше не передаём theme и locale явно
    }
}

// 3. Использование в любом дочернем composable без явной передачи
@Composable
fun UserProfile() {
    val theme = LocalAppTheme.current // Получаем текущее значение
    val locale = LocalLocale.current
    
    Text(
        text = "Привет", 
        color = theme.primary,
        fontSize = locale.fontSize
    )
}

Ключевые особенности и лучшие практики

  • Типы CompositionLocal:
    * **`compositionLocalOf`** — пересчитывает подписчиков при изменении значения
    * **`staticCompositionLocalOf`** — пересчитывает ВСЕ дочерние composables при изменении (более эффективен для редко меняющихся данных)

  • Область применения — CompositionLocal следует использовать для данных, которые действительно являются скопированными (глобальными в пределах поддерева):
    * Тема оформления (`LocalContext`, `LocalConfiguration`)
    * Ресурсы (`LocalContext.resources`)
    * Навигация (в некоторых архитектурах)
    * Состояние воспроизведения медиа
    * Пользовательские предпочтения

  • Чего следует избегать:
    * Не используйте для передачи бизнес-логики или состояний, специфичных для конкретного экрана
    * Не злоупотребляйте — чрезмерное использование усложняет понимание потока данных
    * Не используйте как замену правильно организованному состоянию (`ViewModel`, `StateFlow`)

Встроенные CompositionLocal в Compose

Jetpack Compose предоставляет множество готовых CompositionLocal:

@Composable
fun Example() {
    val context = LocalContext.current
    val configuration = LocalConfiguration.current
    val density = LocalDensity.current
    val focusManager = LocalFocusManager.current
    
    // Использование встроенных значений
    Button(onClick = { /* */ }) {
        Text("Кнопка")
    }
}

Когда использовать CompositionLocal

  • Тема и стилизация — самый распространённый случай
  • Локализация и ресурсы
  • Доступ к системным сервисам (Context, Resources)
  • Конфигурация устройства (плотность пикселей, размер экрана)
  • Логирование и мониторинг (передача логгера или аналитики)

Важное замечание: Современные практики рекомендуют использовать CompositionLocal вместе с Dependency Injection (например, через Hilt), где DI-фреймворк предоставляет зависимости, а CompositionLocal распространяет их по дереву Compose.

Пример с пользовательской темой

// Объявление своей темы
data class CustomTheme(
    val primaryColor: Color,
    val typography: Typography,
    val padding: Dp
)

val LocalCustomTheme = compositionLocalOf<CustomTheme> {
    error("CustomTheme не предоставлена")
}

// Предоставление
@Composable
fun MyApp() {
    val myTheme = CustomTheme(
        primaryColor = Color.Blue,
        typography = Typography(),
        padding = 16.dp
    )
    
    CompositionLocalProvider(LocalCustomTheme provides myTheme) {
        Content()
    }
}

// Использование
@Composable
fun Content() {
    val theme = LocalCustomTheme.current
    
    Column(
        modifier = Modifier.padding(theme.padding)
    ) {
        Text(
            text = "Заголовок",
            style = theme.typography.h1,
            color = theme.primaryColor
        )
    }
}

CompositionLocal — это мощный инструмент, который при правильном использовании значительно упрощает архитектуру Compose-приложений, уменьшая шаблонный код и делая данные доступными именно там, где они нужны.

Что такое CompositionLocal в Jetpack Compose? | PrepBro