Introduction: Why Context in Jetpack Compose is a Headache for Beginners?
\nIf you've ever written Android apps using XML, you know that Context is literally the "key" to everything: from accessing resources to launching new Activities. But when you switch to Jetpack Compose, everything changes. Composable functions don't have direct access to Context, and this often becomes a stumbling block for developers, especially those just starting to learn the declarative approach.
The question "How to get Context in Jetpack Compose" on Stack Overflow has gathered thousands of views and dozens of answers. This is no coincidence. In this article, we'll cover all possible ways to get Context in Compose, from simple to advanced, with real code examples in Kotlin. You'll learn when to use LocalContext.current, how to avoid memory leaks, and which practices are considered "best practices" in 2025.
1. The Simplest Way: LocalContext.current
\nIf you need to get Context inside a composable function, the most straightforward way is to use LocalContext.current. This is a composable local that provides the current Context (usually an Activity or ContextWrapper).
@Composable\nfun MyScreen() {\n val context = LocalContext.current\n \n Button(onClick = {\n Toast.makeText(context, "Hello from Compose!", Toast.LENGTH_SHORT).show()\n }) {\n Text("Show Toast")\n }\n}\n\nThis code works, but there's a nuance: LocalContext.current gives you a Context that is tied to the current CompositionLocal. In most cases, this is what you need. However, remember that such a Context should not be passed to a ViewModel or stored in long-lived objects — this can lead to memory leaks.
When to use this?
\n- \n
- For one-time UI actions: Toast, Snackbar, launching an Activity. \n
- For accessing resources:
context.getString(R.string.app_name). \n - For working with system services:
context.getSystemService(). \n
2. Context in ViewModel: The Correct Approach
\nOne of the most common mistakes is passing Context directly to a ViewModel. In Android Architecture Components, this is considered an anti-pattern because a ViewModel can outlive an Activity, and the Context will become stale. Instead, use AndroidViewModel.
class MyViewModel(application: Application) : AndroidViewModel(application) {\n \n private val context = getApplication<Application>().applicationContext\n \n fun loadData() {\n // Use applicationContext, which is safe\n val prefs = context.getSharedPreferences("my_prefs", Context.MODE_PRIVATE)\n }\n}\n\nIn Compose, you can get an instance of such a ViewModel in the standard way:
\n\n@Composable\nfun MyScreen() {\n val viewModel: MyViewModel = viewModel()\n // Context is already inside the ViewModel, no need to pass from UI\n}\n\nAdvantage: applicationContext lives as long as the process lives and does not depend on the Activity lifecycle. This is safe and recommended by Google.
3. Context in CompositionLocal: Custom Providers
\nSometimes you need to pass a specific Context (e.g., for testing or for a library module). In such cases, you can create your own CompositionLocal.
val LocalCustomContext = staticCompositionLocalOf<Context> {\n error("No custom context provided")\n}\n\n@Composable\nfun CustomContextProvider(context: Context, content: @Composable () -> Unit) {\n CompositionLocalProvider(LocalCustomContext provides context) {\n content()\n }\n}\n\n// Usage:\n@Composable\nfun MyScreen() {\n val customContext = LocalCus