Avatar ·

Как получить Context в Jetpack Compose (Kotlin)

Привет, коллеги! Я только начал осваивать Jetpack Compose и сразу столкнулся с проблемой. В старом XML-подходе я привык получать Context через `this` или `getApplicationContext()`, но в Compose это не работает. Мне нужно получить Context, чтобы, например, запустить `Toast`, получить доступ к ресурсам (`strings.xml`, цвета) или к системным сервисам, таким как `ConnectivityManager`. Вот пример моего кода, который не компилируется: ```kotlin @Composable fun MyScreen() { // Пытаюсь получить Context как обычно val context = this // Ошибка: 'this' не является Context // Или так: val context = getApplicationContext() // Ошибка: функция не определена Button(onClick = { Toast.makeText(context, "Нажато!", Toast.LENGTH_SHORT).show() }) { Text("Показать Toast") } } ``` Я понимаю, что Compose — это декларативный UI, и доступ к Context должен быть организован иначе. Но как именно? Нужно ли передавать Context в параметры? Или есть специальный API? Я слышал про `LocalContext`, но не уверен, как его правильно использовать. Также меня интересует: как получить Activity-специфичный Context (например, для `startActivity`)? И в чём разница между `LocalContext.current` и `LocalLifecycleOwner.current`? Буду благодарен за подробный ответ с рабочими примерами на Kotlin. Спасибо! <<>>

Правильный способ получения Context в Jetpack Compose

В Jetpack Compose для доступа к Context используется композиционный локальный объект LocalContext. Это часть системы CompositionLocal, которая предоставляет значения, автоматически распространяемые вниз по дереву композиции. Не нужно передавать Context вручную через параметры — это нарушило бы принципы декларативного подхода и усложнило бы код.

Вот как правильно получить Context в вашем примере с Toast:

import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import android.widget.Toast
import androidx.compose.material3.Button
import androidx.compose.material3.Text

@Composable
fun MyScreen() {
    // Получаем Context через LocalContext
    val context = LocalContext.current
    
    Button(onClick = {
        Toast.makeText(context, "Нажато!", Toast.LENGTH_SHORT).show()
    }) {
        Text("Показать Toast")
    }
}

Пояснение работы LocalContext

LocalContext.current возвращает объект Context, который соответствует текущей Activity или Fragment, в котором размещён ваш Composable. В большинстве случаев это Activity-контекст, которого достаточно для запуска новых Activity, доступа к ресурсам и системным сервисам.

Доступ к ресурсам

С помощью Context вы можете получать строки, цвета, размеры и другие ресурсы:

@Composable
fun Greeting() {
    val context = LocalContext.current
    val appName = context.getString(R.string.app_name)
    val primaryColor = context.getColor(R.color.primary)
    
    Text(
        text = "Добро пожаловать в $appName",
        color = Color(primaryColor)
    )
}

Работа с системными сервисами

Для доступа к сервисам, таким как ConnectivityManager, используйте тот же Context:

@Composable
fun NetworkStatus() {
    val context = LocalContext.current
    val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    
    // Далее можно проверять статус сети
}

Разница с LocalLifecycleOwner

LocalLifecycleOwner.current возвращает объект LifecycleOwner (обычно Activity или Fragment), который позволяет подписываться на жизненный цикл. Это другой инструмент — он не заменяет Context. Используйте его, когда вам нужно реагировать на события жизненного цикла (например, запускать корутину при старте Activity).

Важные замечания

  • Не храните Context в переменных вне композиции. Это может привести к утечкам памяти. Используйте LocalContext.current только внутри @Composable функций или лямбд.
  • Для ViewModel Context не нужен — используйте AndroidViewModel, если требуется Application контекст.
  • Для startActivity используйте Intent(context, TargetActivity::class.java) — это работает корректно с Activity-контекстом.
  • В Preview (аннотация @Preview) LocalContext может быть недоступен или возвращать mock-контекст. Для Preview лучше не использовать Context-зависимые операции.

Альтернативный подход: передача Context через параметры

Хотя это не рекомендуется, вы можете передать Context в качестве параметра, если ваш Composable используется только в одном месте. Но это нарушает переиспользуемость и усложняет тестирование:

@Composable
fun MyScreen(context: Context) {
    Button(onClick = {
        Toast.makeText(context, "Нажато!", Toast.LENGTH_SHORT).show()
    }) { ... }
}

Используйте LocalContext.current — это стандартный идиоматичный способ в Jetpack Compose. Он оптимизирован под реактивное обновление и не создаёт проблем с памятью при правильном использовании.

<<>>

Использование remember для оптимизации

Если вы получаете Context внутри лямбды (например, в onClick), можно использовать remember, чтобы избежать повторного чтения при каждой рекомпозиции. Это особенно полезно, если Context нужен в нескольких местах одного Composable:

@Composable
fun MyScreen() {
    val context = remember { LocalContext.current }
    
    Button(onClick = {
        Toast.makeText(context, "Кнопка 1", Toast.LENGTH_SHORT).show()
    }) {
        Text("Кнопка 1")
    }
    
    Button(onClick = {
        Toast.makeText(context, "Кнопка 2", Toast.LENGTH_SHORT).show()
    }) {
        Text("Кнопка 2")
    }
}

remember гарантирует, что Context будет получен только один раз за время жизни Composable в композиции. Это не обязательно для производительности (LocalContext.current — лёгкая операция), но делает код чище и явно показывает, что Context не меняется.

Для Activity-специфичных задач

Если вам нужно получить саму Activity (например, для вызова finish() или startActivityForResult), используйте приведение типа:

val activity = LocalContext.current as? AppCompatActivity
activity?.finish() // Закрыть Activity

Будьте осторожны с таким приведением — оно может выбросить исключение, если Context не является Activity. Проверяйте тип через оператор is или используйте as? для безопасного приведения.