@ApplicationContext vs Activity Context: When to Use Each in Hilt
The Problem
I was reviewing an Android developer quiz on Reddit when a comment by Zhuinden caught my attention. He was discussing a potential quiz question about context injection in Hilt, specifically for image loading with Glide.
His point was sharp: “Glide uses Glide.with(activity) and Glide.with(fragment) to scope the image loading request, you can’t just pass it a Context as is, especially not an application context.”
This highlights a common confusion I see in Android development. When Hilt asks you to inject a Context, you have choices: @ApplicationContext, @ActivityContext, or just inject Activity directly. The wrong choice doesn’t just cause build errors—it causes memory leaks.
Why Context Choice Matters
In Android, Context is not a single thing. You have:
Context | +-- Application Context (lives for app lifetime) | +-- Activity Context (lives for activity lifetime) | +-- Fragment Context (lives for fragment lifetime)The key difference is lifecycle. Application context survives until your app is killed. Activity context dies when the activity is destroyed. When you inject the wrong context into a long-lived object, you create a memory leak.
flowchart TD A[Context Types] --> B[Application Context] A --> C[Activity Context]
B --> D[Lifecycle: App lifetime] B --> E[Use for: Singletons, Database, SharedPreferences]
C --> F[Lifecycle: Activity lifetime] C --> G[Use for: UI operations, Glide, LayoutInflater]
D --> H[Memory-safe in singletons] F --> I[DANGEROUS in singletons - memory leak!]
style I fill:#ffccccWhen to Use @ApplicationContext
Use @ApplicationContext when your dependency needs to survive across the entire app lifecycle. This is the safe choice for singletons because application context won’t be garbage collected while your app is running.
SharedPreferences
@Module@InstallIn(SingletonComponent::class)object AppModule {
@Provides @Singleton fun provideSharedPreferences( @ApplicationContext context: Context ): SharedPreferences { return context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE) }}SharedPreferences files belong to the application, not any specific activity. Using application context here is correct.
Room Database
@Module@InstallIn(SingletonComponent::class)object DatabaseModule {
@Provides @Singleton fun provideDatabase( @ApplicationContext context: Context ): AppDatabase { return Room.databaseBuilder( context, AppDatabase::class.java, "app_database" ).build() }}Database files persist across activities. The database singleton should live as long as the app, so application context is the right choice.
Analytics and Logging Services
@Singletonclass AnalyticsService @Inject constructor( @ApplicationContext private val context: Context) { fun trackEvent(name: String, params: Map<String, Any>) { // Analytics needs app-wide context for device info // It tracks events across all activities }}Analytics services track user behavior across the entire app lifecycle. They shouldn’t be tied to any specific activity.
When to Use Activity Context
Use activity context when your dependency is tied to UI operations or needs lifecycle awareness. This is critical for:
- Operations that should stop when the activity dies
- APIs that require activity-specific behavior
- Resources that depend on the current activity theme
The Glide Case: Why Context Matters
This is where many developers go wrong. Glide explicitly requires activity or fragment context for proper lifecycle management:
@ActivityScopedclass ImageLoader @Inject constructor( private val activity: Activity // Hilt provides this automatically) { fun loadImage(url: String, imageView: ImageView) { Glide.with(activity) // Proper lifecycle scoping! .load(url) .into(imageView) }}Why does Glide need activity context? Because Glide pauses loading when the activity pauses, and cancels requests when the activity is destroyed. If you pass application context, Glide can’t manage the lifecycle properly:
@Module@InstallIn(SingletonComponent::class)object WrongModule {
@Provides @Singleton fun provideImageLoader( @ApplicationContext context: Context // DANGER! ): ImageLoader { // This will load images even after activities are destroyed // causing memory leaks and wrong lifecycle behavior return GlideImageLoader(context) }}With application context, Glide has no way to know when to pause or cancel requests. Images might load into destroyed activities, causing crashes and memory leaks.
Resource Access with Theme
@ActivityScopedclass ResourceHelper @Inject constructor( @ActivityContext private val context: Context) { fun getString(resId: Int): String = context.getString(resId)
fun getDrawable(resId: Int): Drawable? { // Activity context respects the current theme return ContextCompat.getDrawable(context, resId) }
fun getThemedColor(attrResId: Int): Int { val typedValue = TypedValue() context.theme.resolveAttribute(attrResId, typedValue, true) return typedValue.data }}Activity context respects the activity’s theme. Application context uses the application theme, which might differ. If your activities use different themes, you need activity context for correct resource resolution.
Three Ways to Inject Context in Hilt
Hilt gives you three options for context injection:
class ExampleClass @Inject constructor( @ApplicationContext private val appContext: Context, // Application context @ActivityContext private val activityContext: Context, // Activity context private val activity: Activity // Activity directly (activity-scoped only)) { fun demonstrateUsage() { // appContext: Use for SharedPreferences, Database, Analytics // activityContext: Use for Resources, LayoutInflater, theme-aware operations // activity: Use when you need Activity-specific APIs (startActivityForResult, etc.) }}Option 1: @ApplicationContext
Available in any scope. Use for app-wide singletons.
@Singletonclass AppDatabase @Inject constructor( @ApplicationContext private val context: Context) { // Lives as long as the app}Option 2: @ActivityContext
Only available in ActivityComponent or ActivityRetainedComponent. Use for activity-scoped dependencies.
@ActivityScopedclass ActivityResourceProvider @Inject constructor( @ActivityContext private val context: Context) { // Lives as long as the activity}Option 3: Inject Activity Directly
Only available in ActivityComponent. Use when you need Activity-specific methods.
@ActivityScopedclass Navigator @Inject constructor( private val activity: Activity) { fun startActivityForResult(intent: Intent, requestCode: Int) { activity.startActivityForResult(intent, requestCode) }
fun finish() { activity.finish() }}Scoping Implications
The context you inject must match your dependency’s scope:
┌─────────────────────────────────────────────────────────────────┐│ Scope │ Allowed Context Types │├─────────────────────────────────────────────────────────────────┤│ SingletonComponent │ @ApplicationContext only ││ ActivityRetainedComp │ @ApplicationContext, @ActivityContext ││ ActivityComponent │ @ApplicationContext, @ActivityContext, ││ │ Activity (direct) ││ FragmentComponent │ Same as ActivityComponent │└─────────────────────────────────────────────────────────────────┘Singleton with Activity Context = Memory Leak
// WRONG: Singleton holding activity context@Singleton // Lives foreverclass BadSingleton @Inject constructor( @ActivityContext private val context: Context // Activity dies, but singleton holds reference) { // Memory leak! Activity can't be garbage collected}When the activity is destroyed, the singleton still holds a reference to it. The activity can’t be garbage collected until the app is killed.
Activity-Scoped with Application Context = OK
// OK: Activity-scoped with application context@ActivityScopedclass ActivityRepository @Inject constructor( @ApplicationContext private val context: Context // Application context outlives activity) { // No leak - application context lives as long as app}This is safe because application context lives longer than the activity. No memory leak occurs.
Practical Decision Guide
Here’s a simple decision tree:
Need a Context? │ ├─ Is the dependency a Singleton? │ └─ YES → Must use @ApplicationContext │ ├─ Does the dependency need lifecycle awareness? │ ├─ YES → Use @ActivityContext or Activity │ │ │ └─ NO → Use @ApplicationContext (simpler) │ ├─ Does it need Activity-specific APIs? │ └─ YES → Inject Activity directly │ └─ Does it need theme-aware resources? └─ YES → Use @ActivityContextQuick Reference Table
| Dependency Type | Recommended Context | Reason |
|---|---|---|
| SharedPreferences | @ApplicationContext | App-wide storage |
| Room Database | @ApplicationContext | Singleton scope |
| Retrofit/OkHttp | @ApplicationContext | Singleton scope |
| Analytics | @ApplicationContext | App-wide tracking |
| Glide/ImageLoader | @ActivityContext or Activity | Lifecycle awareness |
| LayoutInflater | @ActivityContext | Theme-aware inflation |
| Resource resolver | @ActivityContext | Theme-aware resources |
| Navigator | Activity | Activity-specific APIs |
Compose Considerations
In Compose, you typically don’t inject context via Hilt. Instead, use LocalContext.current:
@Composablefun ProfileImage( imageUrl: String, modifier: Modifier = Modifier) { val context = LocalContext.current
// Coil example - automatically lifecycle-aware AsyncImage( model = imageUrl, contentDescription = null, modifier = modifier )}
@Composablefun rememberImageLoader(): ImageLoader { val context = LocalContext.current return remember(context) { ImageLoader.Builder(context) .components { // Configure components } .build() }}Compose’s LocalContext.current provides the appropriate context based on the composition’s lifecycle. For image loading in Compose, Coil handles lifecycle automatically.
Common Mistakes
Mistake 1: Singleton with @ActivityContext
// WRONG - Won't even compile@Singletonclass BadAnalytics @Inject constructor( @ActivityContext private val context: Context // Error!) { }
// Hilt prevents this at compile time with:// "android.app.Activity cannot be provided in SingletonComponent"Mistake 2: Using ApplicationContext for Glide
// PROBLEMATIC - Compiles but causes issues@Singletonclass ImageManager @Inject constructor( @ApplicationContext private val context: Context) { fun load(url: String, view: ImageView) { Glide.with(context) // No lifecycle management! .load(url) .into(view) }}This compiles but Glide can’t manage the request lifecycle. If the activity is destroyed, the image might still load into a dead view.
Mistake 3: Injecting Context Where It’s Not Needed
// UNNECESSARY - Context not actually usedclass UserRepository @Inject constructor( @ApplicationContext private val context: Context, // Not used! private val api: UserApi) { suspend fun getUsers() = api.getUsers() // No context usage}Only inject context when you actually need it. Every injected dependency adds complexity.
Summary
Context injection in Hilt is not a random choice. It has real implications for memory management and lifecycle behavior:
- @ApplicationContext is for singleton-scoped dependencies and app-wide services that don’t need lifecycle awareness
- @ActivityContext is for activity-scoped dependencies that need theme-aware resources or lifecycle binding
- Activity injection is for when you need Activity-specific APIs like
startActivityForResult
The Glide example from Reddit perfectly illustrates why this matters. Using @ApplicationContext for image loading breaks lifecycle management, leading to memory leaks and crashes.
When in doubt, ask yourself: “What is the scope of this dependency, and does it need to know when the activity dies?” The answer tells you which context to inject.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Hilt Documentation
- 👨💻 Context in Android
- 👨💻 Glide Documentation
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments