Live
β€’Black Hat USADark Readingβ€’Black Hat AsiaAI Businessβ€’90 Autonomous Runs: What an AI Agent Society Actually Looks LikeDEV Communityβ€’What is an MCP proxy and why does it need an approval layer?DEV Communityβ€’AI subscriptions are subsidized. Here's what happens when that stops.DEV Communityβ€’I Built a Multi-Agent AI Runtime in Go Because Python Wasn't an OptionDEV Communityβ€’Billionaire Philippe Laffont Sold CoreWeave and Bought This Artificial Intelligence (AI) Stock Instead - The Motley FoolGoogle News: AIβ€’TSMC vs. Nvidia: Which AI Supercycle Growth Stock Is the Better Long-Term Buy? - AOL.comGNews AI NVIDIAβ€’Generative AI Business Use Cases 2026: The 11 Applications Delivering Real ROI - BBN TimesGoogle News: Generative AIβ€’TSMC vs. Nvidia: Which AI Supercycle Growth Stock Is the Better Long-Term Buy? - Yahoo FinanceGNews AI NVIDIAβ€’[P] MCGrad: fix calibration of your ML model in subgroupsReddit r/MachineLearningβ€’TSMC vs. Nvidia: Which AI Supercycle Growth Stock Is the Better Long-Term Buy? - The Motley FoolGNews AI NVIDIAβ€’NVIDIA’s US$2b Marvell Bet Extends AI Platform Into Telecom Networks - simplywall.stGNews AI NVIDIAβ€’Gemma4 26B A4B runs easily on 16GB MacsReddit r/LocalLLaMAβ€’Black Hat USADark Readingβ€’Black Hat AsiaAI Businessβ€’90 Autonomous Runs: What an AI Agent Society Actually Looks LikeDEV Communityβ€’What is an MCP proxy and why does it need an approval layer?DEV Communityβ€’AI subscriptions are subsidized. Here's what happens when that stops.DEV Communityβ€’I Built a Multi-Agent AI Runtime in Go Because Python Wasn't an OptionDEV Communityβ€’Billionaire Philippe Laffont Sold CoreWeave and Bought This Artificial Intelligence (AI) Stock Instead - The Motley FoolGoogle News: AIβ€’TSMC vs. Nvidia: Which AI Supercycle Growth Stock Is the Better Long-Term Buy? - AOL.comGNews AI NVIDIAβ€’Generative AI Business Use Cases 2026: The 11 Applications Delivering Real ROI - BBN TimesGoogle News: Generative AIβ€’TSMC vs. Nvidia: Which AI Supercycle Growth Stock Is the Better Long-Term Buy? - Yahoo FinanceGNews AI NVIDIAβ€’[P] MCGrad: fix calibration of your ML model in subgroupsReddit r/MachineLearningβ€’TSMC vs. Nvidia: Which AI Supercycle Growth Stock Is the Better Long-Term Buy? - The Motley FoolGNews AI NVIDIAβ€’NVIDIA’s US$2b Marvell Bet Extends AI Platform Into Telecom Networks - simplywall.stGNews AI NVIDIAβ€’Gemma4 26B A4B runs easily on 16GB MacsReddit r/LocalLLaMA
AI NEWS HUBbyEIGENVECTOREigenvector

Day 6/100: Context in Android β€” The Wrong One Will Leak Your Entire Activity

DEV Communityby Hoang SonApril 1, 202611 min read1 views
Source Quiz

<p><em>This is Day 6 of my <a href="https://dev.to/hoangshawn/series/37575">100 Days to Senior Android Engineer</a> series. Each post: what I thought I knew β†’ what I actually learned β†’ interview implications.</em></p> <h2> πŸ” The concept </h2> <p><code>Context</code> is one of those Android classes you use dozens of times per day without thinking about it. You pass it to constructors, use it to inflate views, start activities, access resources.</p> <p>But <code>Context</code> isn't one thing β€” it's a family of related objects with very different lifetimes. Pass the wrong one into a long-lived object, and you've just anchored that object to a screen that the user may have left minutes ago. The screen can't be garbage collected. You've created a memory leak.</p> <p>Not a theoretical one. A r

This is Day 6 of my 100 Days to Senior Android Engineer series. Each post: what I thought I knew β†’ what I actually learned β†’ interview implications.

πŸ” The concept

Context is one of those Android classes you use dozens of times per day without thinking about it. You pass it to constructors, use it to inflate views, start activities, access resources.

But Context isn't one thing β€” it's a family of related objects with very different lifetimes. Pass the wrong one into a long-lived object, and you've just anchored that object to a screen that the user may have left minutes ago. The screen can't be garbage collected. You've created a memory leak.

Not a theoretical one. A real one that affects every user who navigates back to that screen.

πŸ’‘ What I thought I knew

My rule of thumb used to be: "Use applicationContext when in doubt."

That's not wrong exactly β€” applicationContext won't leak. But it's incomplete, and applying it blindly causes a different class of bugs: UI operations that crash, theme-aware resources that return wrong values, dialogs that fail to show.

The full picture is more nuanced, and more interesting.

😳 What I actually learned

What Context actually is

Context is an abstract class that provides access to application-level resources and operations:

Enter fullscreen mode

Exit fullscreen mode

Every Activity is a Context. Every Application is a Context. They wrap the same underlying ContextImpl but expose different capabilities and, critically, have different lifetimes.

The lifetime mismatch that causes leaks

The classic pattern that causes a leak:

fun init(context: Context) { // If caller passes an Activity, this singleton now holds // a reference to that Activity for the lifetime of the process this.context = context } }

// In Activity: override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ImageLoader.init(this) // πŸ’€ leaking 'this' into a singleton }`

Enter fullscreen mode

Exit fullscreen mode

The singleton lives forever. It holds the Activity. The Activity holds its entire view hierarchy β€” every View, every Bitmap, every Drawable. None of it can be garbage collected.

Rotate the screen twice and you have three leaked Activity instances, each holding their full view hierarchy in memory.

The fix is one word:

Enter fullscreen mode

Exit fullscreen mode

applicationContext has the same lifetime as the process. No Activity is held. No leak.

But applicationContext isn't always correct

Here's where my old rule of thumb breaks down. applicationContext doesn't have a theme. It doesn't know which Activity it's in. For operations that depend on the UI context, it either crashes or silently returns wrong results.

// βœ… Inflate with Activity context β€” theme attributes resolved correctly val view = LayoutInflater.from(this).inflate(R.layout.my_view, null)`

Enter fullscreen mode

Exit fullscreen mode

// βœ… Show Dialog with Activity context AlertDialog.Builder(this) .setMessage("Are you sure?") .show()`

Enter fullscreen mode

Exit fullscreen mode

// βœ… With the required flag applicationContext.startActivity( Intent(applicationContext, DetailActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK } )`

Enter fullscreen mode

Exit fullscreen mode

The decision table

After going through the docs carefully, this is the table I now keep in my head:

Operation Application Context Activity Context

Start an Activity ⚠️ needs NEW_TASK flag βœ…

Start a Service βœ… βœ…

Send a Broadcast βœ… βœ…

Load a resource (string, drawable) βœ… βœ…

Inflate a layout with theme ❌ wrong theme βœ…

Show a Dialog ❌ crashes βœ…

Create a ViewModel ❌ βœ…

Initialize a singleton / library βœ… ❌ leaks

Access SharedPreferences βœ… βœ…

Get system services (e.g. ClipboardManager) βœ… βœ…

The pattern: anything UI-related needs Activity context. Anything that lives longer than a screen needs Application context.

The this vs requireContext() vs requireActivity() confusion in Fragments

Fragments add another layer of confusion because they're not a Context themselves β€” they have a context.

class MyFragment : Fragment() {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

// 'this' β€” the Fragment, NOT a Context // Can't be passed where Context is expected

// requireContext() β€” the Activity context (or throws if detached) val prefs = requireContext().getSharedPreferences("prefs", Context.MODE_PRIVATE)

// requireActivity() β€” explicitly the Activity, useful when you need // Activity-specific APIs like ViewModelProvider, window insets, etc. val windowInsetsController = requireActivity().window.insetsController

// context β€” nullable version of requireContext(), safe if you check context?.let { ctx -> Toast.makeText(ctx, "Hello", Toast.LENGTH_SHORT).show() } } }`

Enter fullscreen mode

Exit fullscreen mode

The practical rule in Fragments: use requireContext() for Context needs, requireActivity() only when you specifically need the Activity. The difference matters if you ever move the Fragment to a different host.

The WeakReference anti-pattern

You'll sometimes see this pattern as an attempted fix for Context leaks:

fun doSomethingWithUI() { val ctx = contextRef.get() ?: return // use ctx } }`

Enter fullscreen mode

Exit fullscreen mode

This is better than holding a strong reference, but it's still wrong for different reasons:

  • If you need the UI context to still be alive, a WeakReference that's been collected gives you null at an unpredictable time β€” leading to silent no-ops or hard-to-reproduce bugs.

  • If you don't need the UI context, you should be using applicationContext anyway.

WeakReference is almost never the right answer. It papers over the real issue: the object's lifetime is incompatible with the context's lifetime. Fix the lifetime problem instead.

The ContextThemeWrapper you didn't know you were using

When you write LayoutInflater.from(activity), you get a LayoutInflater that uses the Activity's theme. But sometimes you need to inflate a view with a different theme than the current Activity β€” for example, inflating a dark-themed bottom sheet inside a light-themed Activity.

Enter fullscreen mode

Exit fullscreen mode

ContextThemeWrapper wraps any Context and overlays a theme on top. Compose's MaterialTheme uses this mechanism internally when you apply a LocalContentColor or a theme override. Knowing it exists saves you from the wrong solution β€” changing the Activity theme globally just to style one component.

πŸ§ͺ The rule of thumb that actually works

Instead of "use applicationContext when in doubt", the rule I use now:

Match the context's lifetime to the consumer's lifetime.

  • Consumer lives as long as the process (singleton, repository, library init) β†’ applicationContext

  • Consumer lives as long as a screen (ViewModel, Adapter, custom View within an Activity) β†’ Activity context, and make sure the consumer doesn't outlive the screen

  • Consumer is inside a Fragment β†’ requireContext(), which gives you the host Activity's context

// If receiver is longer-lived β†’ applicationContext // If receiver needs UI β†’ Activity context, manage its lifecycle`

Enter fullscreen mode

Exit fullscreen mode

❓ The interview questions

Question 1 β€” Mid-level:

"What's the difference between this, applicationContext, and baseContext in an Activity?"

  • this β€” the Activity itself, which is a Context. Full UI capabilities, lifetime tied to the Activity.

  • applicationContext β€” the Application object. No UI capabilities, lives as long as the process.

  • baseContext β€” the wrapped ContextImpl inside the ContextWrapper. Rarely used directly; it's the raw context before any wrapper applies its logic. Calling baseContext from an Activity skips the Activity's own context wrapper logic β€” almost never what you want.

Question 2 β€” Senior:

"You're building an SDK that other apps will integrate. Your SDK has a singleton that needs to perform operations like loading resources and starting a background service. How do you handle Context?"

class MySdk private constructor(private val appContext: Context) {

companion object { @Volatile private var instance: MySdk? = null

fun init(context: Context): MySdk { return instance ?: synchronized(this) { instance ?: MySdk( // Always store applicationContext β€” the caller's Activity // will be destroyed; the SDK must outlive it context.applicationContext ).also { instance = it } } } }

fun loadResource(): String { // βœ… Resources work fine with applicationContext return appContext.getString(R.string.sdk_label) }

fun startBackgroundWork() { // βœ… Starting a Service works with applicationContext appContext.startService(Intent(appContext, SdkSyncService::class.java)) }

fun showDialog(activity: Activity) { // βœ… For UI operations, require the caller to pass Activity explicitly // Don't store it β€” just use it for this operation AlertDialog.Builder(activity) .setMessage("SDK needs permission") .show() } }`

Enter fullscreen mode

Exit fullscreen mode

Key points: always store applicationContext in the singleton, never the caller's Activity. For UI operations, make them instance methods that take an Activity parameter rather than storing it.

Question 3 β€” Senior:

"LeakCanary reports a Context leak in your app, originating from a custom View. How do you diagnose and fix it?"

First, understand what LeakCanary is telling you: the Context (likely an Activity) is retained after it should have been garbage collected, because something is holding a reference to it.

For a custom View, the most common causes:

// Cause 2: Anonymous listener registered on a long-lived object class MyView(context: Context) : View(context) { init { EventBus.register(object : EventListener { // πŸ’€ anonymous class holds outer View override fun onEvent(event: Event) { /* ... */ } }) // EventBus holds the listener, listener holds the View, View holds Context } }

// Fix: use weak reference or unregister in onDetachedFromWindow override fun onDetachedFromWindow() { super.onDetachedFromWindow() EventBus.unregister(listener) }

// Cause 3: Context stored in a non-View object without applicationContext class ImageCache(val context: Context) { // πŸ’€ if passed Activity context // ... } // Fix: ImageCache(context.applicationContext)`

Enter fullscreen mode

Exit fullscreen mode

Diagnosis approach: read the LeakCanary reference chain bottom-up. The chain shows exactly which object is holding the reference that prevents GC. The fix is usually at the top of that chain β€” nulling a reference, unregistering a listener, or swapping to applicationContext.

What surprised me revisiting this

  • applicationContext for LayoutInflater doesn't just look wrong β€” it silently breaks theme-dependent attributes without throwing an exception. Views inflate but render incorrectly. The kind of bug that shows up in screenshots and is hard to reproduce in isolation.

  • WeakReference is a false sense of security. I've written this pattern before and thought I was being careful. Going back through the theory, it just moves the problem rather than fixing it.

  • ContextThemeWrapper being the mechanism behind Compose's LocalContentColor β€” I'd used Compose theme overrides for months without ever connecting it to the View system's ContextThemeWrapper. The mental model clicked when I saw them as the same concept.

Tomorrow

Day 7 β†’ Week 1 Recap β€” six posts in, and I'm already finding more gaps than I expected. A summary of the most important insights from this week, plus the one question I couldn't answer confidently until now.

What's the most surprising Context-related bug you've encountered? Drop it in the comments.

← Day 5: Intent, Task & Back Stack β€” Why launchMode Is a Trap

Was this article helpful?

Sign in to highlight and annotate this article

AI
Ask AI about this article
Powered by Eigenvector Β· full article context loaded
Ready

Conversation starters

Ask anything about this article…

Daily AI Digest

Get the top 5 AI stories delivered to your inbox every morning.

Knowledge Map

Knowledge Map
TopicsEntitiesSource
Day 6/100: …modellaunchversionapplicationservicereportDEV Communi…

Connected Articles β€” Knowledge Graph

This article is connected to other articles through shared AI topics and tags.

Building knowledge graph…

Discussion

Sign in to join the discussion

No comments yet β€” be the first to share your thoughts!