Контекст в Jetpack Compose: Полный гайд с примерами кода

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему Context в Jetpack Compose — это головная боль новичков?

Если вы когда-либо писали на Android с использованием XML, то знаете, что Context — это буквально «ключ» ко всему: от доступа к ресурсам до запуска новых Activity. Но когда вы переходите на Jetpack Compose, всё меняется. Композитные функции не имеют прямого доступа к Context, и это часто становится камнем преткновения для разработчиков, особенно тех, кто только начинает осваивать декларативный подход.

Вопрос «How to get Context in Jetpack Compose» на Stack Overflow собрал тысячи просмотров и десятки ответов. Это не случайно. В этой статье мы разберём все возможные способы получить Context в Compose, от простых до продвинутых, с реальными примерами кода на Kotlin. Вы узнаете, когда использовать LocalContext.current, как избежать утечек памяти и какие практики считаются «best practices» в 2025 году.

1. Самый простой способ: LocalContext.current

Если вам нужно получить Context внутри композитной функции, самый прямолинейный способ — использовать LocalContext.current. Это composable-локал, который предоставляет текущий Context (обычно это Activity или ContextWrapper).

@Composable
fun MyScreen() {
    val context = LocalContext.current
    
    Button(onClick = {
        Toast.makeText(context, "Привет из Compose!", Toast.LENGTH_SHORT).show()
    }) {
        Text("Показать Toast")
    }
}

Этот код работает, но есть нюанс: LocalContext.current даёт вам Context, который привязан к текущему CompositionLocal. В большинстве случаев это то, что нужно. Однако помните, что такой Context не следует передавать в ViewModel или сохранять в долгоживущих объектах — это может привести к утечкам памяти.

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

  • Для одноразовых действий в UI: Toast, Snackbar, запуск Activity.
  • Для доступа к ресурсам: context.getString(R.string.app_name).
  • Для работы с системными сервисами: context.getSystemService().

2. Context в ViewModel: Правильный подход

Одна из самых частых ошибок — передавать Context напрямую в ViewModel. В Android Architecture Components это считается антипаттерном, так как ViewModel может пережить Activity, и Context станет устаревшим. Вместо этого используйте AndroidViewModel.

class MyViewModel(application: Application) : AndroidViewModel(application) {
    
    private val context = getApplication<Application>().applicationContext
    
    fun loadData() {
        // Используем applicationContext, который безопасен
        val prefs = context.getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
    }
}

В Compose вы можете получить экземпляр такой ViewModel стандартным способом:

@Composable
fun MyScreen() {
    val viewModel: MyViewModel = viewModel()
    // Context уже внутри ViewModel, не нужно передавать из UI
}

Преимущество: applicationContext живёт, пока жив процесс, и не зависит от жизненного цикла Activity. Это безопасно и рекомендуется Google.

3. Context в CompositionLocal: Кастомные провайдеры

Иногда вам нужно передать специфический Context (например, для тестирования или для модуля с библиотекой). В таких случаях можно создать свой CompositionLocal.

val LocalCustomContext = staticCompositionLocalOf<Context> {
    error("No custom context provided")
}

@Composable
fun CustomContextProvider(context: Context, content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalCustomContext provides context) {
        content()
    }
}

// Использование:
@Composable
fun MyScreen() {
    val customContext = LocalCustomContext.current
    // Теперь у вас есть доступ к кастомному контексту
}

Этот подход полезен, когда вы работаете с библиотеками, которые требуют особого Context (например, для DI или для работы с файловой системой).

4. Context для ресурсов: Избегаем дублирования

Одна из частых задач — получить строку, цвет или размер. Вместо того чтобы каждый раз писать context.resources.getString(...), в Compose есть встроенные решения:

@Composable
fun Greeting() {
    val appName = stringResource(R.string.app_name)
    val primaryColor = colorResource(R.color.primary)
    val icon = painterResource(R.drawable.ic_launcher)
    
    Text(text = "Добро пожаловать в $appName", color = primaryColor)
}

Эти функции-обёртки автоматически используют правильный Context и учитывают конфигурационные изменения (например, смену языка). Это предпочтительный способ работы с ресурсами в Compose.

5. Распространённые ошибки и как их избежать

Ошибка 1: Сохранение Context в статической переменной

Никогда не делайте так:

object GlobalContext {
    lateinit var context: Context // Потенциальная утечка!
}

Это может привести к тому, что Activity не будет собрано сборщиком мусора, и вы получите утечку памяти.

Ошибка 2: Использование Activity Context в корутинах

Если вы запускаете корутину в ViewModel, не используйте Context, полученный из UI. Всегда используйте applicationContext или передавайте его через параметры.

// ПЛОХО:
fun doSomething(context: Context) {
    viewModelScope.launch {
        // context может быть устаревшим
    }
}

// ХОРОШО:
class MyViewModel(application: Application) : AndroidViewModel(application) {
    fun doSomething() {
        val appContext = getApplication<Application>()
        viewModelScope.launch {
            // appContext безопасен
        }
    }
}

6. Продвинутый кейс: Context для измерения Display

Иногда вам нужно получить размер экрана или плотность пикселей. Вместо того чтобы тянуть Context, используйте LocalConfiguration:

@Composable
fun ScreenInfo() {
    val configuration = LocalConfiguration.current
    val screenHeight = configuration.screenHeightDp.dp
    val screenWidth = configuration.screenWidthDp.dp
    val density = LocalDensity.current
    
    Text("Экран: ${screenWidth}x${screenHeight}, плотность: ${density.density}")
}

Это чище и не требует прямого доступа к Context.

7. Когда Context не нужен: Альтернативные подходы

Compose предоставляет множество функций, которые заменяют необходимость в Context:

  • LocalContext.current — для редких случаев.
  • remember и LaunchedEffect — для выполнения побочных эффектов.
  • LocalLifecycleOwner — для работы с жизненным циклом.
  • LocalView — если вам нужен доступ к View (редко).

Старайтесь минимизировать использование Context в UI-слое. Чем меньше зависимостей от Android-фреймворка, тем легче тестировать и переиспользовать код.

Итог: Что запомнить?

Получение Context в Jetpack Compose — это не магия, а дисциплина. Вот три главных правила:

  1. Для UI — используйте LocalContext.current, но не передавайте

Блоги

Рекомендаций по книгам