Avatar ·

Как получить Context в Jetpack Compose? Проблема с доступом к ресурсам

Всем привет! Я только начал изучать Jetpack Compose и столкнулся с проблемой: не могу получить Context внутри @Composable функции. Раньше, когда я писал на классическом Android SDK с XML, я просто вызывал getContext() или this в Activity/Fragment. Но в Compose я не понимаю, как это сделать. Вот мой пример. Я пытаюсь показать Toast при нажатии на кнопку: ```kotlin @Composable fun MyScreen() { Button(onClick = { // Здесь нужен Context для Toast Toast.makeText(???, "Нажато!", Toast.LENGTH_SHORT).show() }) { Text("Нажми меня") } } ``` Я пробовал использовать LocalContext.current, но у меня не получается. Подскажите, как правильно получить Context в Jetpack Compose? Нужно ли его передавать через параметры функции? Или есть какой-то встроенный способ? Также интересует: как получить доступ к ресурсам (строки, цвета, размеры) без Context? Буду благодарен за подробное объяснение с примерами. <<>>

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

В Jetpack Compose действительно есть встроенный механизм для получения Context — это композиционная локаль LocalContext. Она предоставляет доступ к текущему Context (обычно это Context Activity или Fragment, в котором размещен Composable).

@Composable
fun MyScreen() {
    // Получаем Context через LocalContext
    val context = LocalContext.current

    Button(onClick = {
        // Теперь можем использовать context
        Toast.makeText(context, "Нажато!", Toast.LENGTH_SHORT).show()
    }) {
        Text("Нажми меня")
    }
}

Важно: LocalContext.current можно вызывать только внутри @Composable функции или лямбды, которая выполняется в контексте композиции. Не пытайтесь использовать его в обычных функциях или блоках инициализации.

Доступ к ресурсам без явного Context

Для работы с ресурсами (строки, цвета, размеры) в Compose есть более удобные способы, которые не требуют прямого доступа к Context:

  • Строки: используйте stringResource(R.string.my_string)
  • Цвета: используйте colorResource(R.color.my_color)
  • Размеры (dimen): используйте dimensionResource(R.dimen.my_dimen)
  • Drawable: используйте painterResource(R.drawable.my_icon)
@Composable
fun ResourceExample() {
    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
    )
    Icon(painter = icon, contentDescription = null)
}

Когда нужно передавать Context через параметры?

В некоторых случаях лучше передавать Context как параметр, особенно если ваша функция не является Composable или вы хотите сделать её тестируемой:

// Плохо: скрытая зависимость от Context
class MyRepository {
    fun saveData() {
        val context = LocalContext.current // Ошибка! Не Composable контекст
    }
}

// Хорошо: явная передача Context
class MyRepository(private val context: Context) {
    fun saveData() {
        // Используем context
    }
}

// В Composable передаём Context в конструктор
@Composable
fun MyScreen() {
    val context = LocalContext.current
    val repository = remember { MyRepository(context) }
}

Важные предостережения

  • Не сохраняйте Context в глобальных переменных — это может привести к утечкам памяти. Используйте remember или DI-контейнеры.
  • Для Activity Result API используйте rememberLauncherForActivityResult — не нужно получать Context.
  • Для работы с системными сервисами (например, LocationManager) получайте Context через LocalContext и вызывайте getSystemService() внутри Composable.

В итоге: для большинства задач в Compose вам не нужен Context напрямую — используйте встроенные Resource-функции. Если же Context всё-таки необходим, обращайтесь к LocalContext.current внутри @Composable функции.

<<>>

Альтернатива: использование CompositionLocalProvider

Если вам нужно заменить Context на другой (например, для тестирования или изоляции), вы можете использовать CompositionLocalProvider:

@Composable
fun CustomContextExample() {
    val testContext = MockContext() // ваш тестовый контекст

    CompositionLocalProvider(LocalContext provides testContext) {
        // Внутри этого блока LocalContext.current вернёт testContext
        MyScreen() 
    }
}

Для доступа к Activity

Если вам нужен именно Activity (например, для запуска Intent), используйте LocalActivity.current (из библиотеки activity-compose):

@Composable
fun MyScreen() {
    val activity = LocalActivity.current
    Button(onClick = {
        activity?.startActivity(Intent(activity, SecondActivity::class.java))
    }) {
        Text("Открыть другую Activity")
    }
}

Этот подход удобен, когда нужно явно указать, какой Context используется в определённой части UI-дерева.