Введение: Почему 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 — это не магия, а дисциплина. Вот три главных правила:
- Для UI — используйте
LocalContext.current, но не передавайте