A deep-dive into two production bugs caused by Don't Keep Activities (DKA) mode on Android, both rooted in the same pattern: state initialized in a lifecycle callback that doesn't fire when a Fragment is recreated with a retained ViewModel. Bug #1 involved a media picker whose callbacks were set on a stale Fragment instance cached via a lazy property, causing a 2.3 MB memory leak per navigation cycle. Bug #2 involved a profile screen's MVI side-effect delegate going uninitialized after DKA restoration, silently swallowing all navigation commands. The fix in both cases was switching from lateinit fields initialized in ViewModel-creation callbacks to Kotlin lazy delegates that initialize on first access. The post also covers a DKA checklist, instrumentation testing with ActivityScenario.recreate(), LeakCanary integration, and how the same bug class appears in Jetpack MVVM, MVIKotlin, and other architectures with asymmetric component lifetimes.

15m read timeFrom proandroiddev.com
Post cover image
Table of contents
A 30-Second Refresher on DKA + Retained ViewModelsBug #1: The Media Picker That Forgot Its CallbacksThe SetupThe BugWhat the Memory Dump RevealsThe DiscoveryThe FixBug #2: The Profile That Forgot How to NavigateThe SetupThe BugGet Vitaliy Gribko ’s stories in your inboxThe Same Pattern, Hiding in Three More PlacesThe FixThe PatternThe DKA Checklist

Questions this post answers

Why does my Android Fragment stop working after navigating away and back with Don't Keep Activities enabled?

With Don't Keep Activities (DKA) enabled, the Activity and Fragment are destroyed on navigation, but the ViewModelStore survives. If critical state — callbacks, delegates, navigation handlers — was initialized in a ViewModel-creation callback like onCreateFeature or ViewModel.init, that initialization does not run again on Fragment recreation. The Fragment gets a fresh instance but the ViewModel is reused, leaving fields uninitialized or pointing to dead objects. Android developers debugging silent DKA failures track patterns like this on daily.dev.

How do I test that my Android Fragment survives activity recreation with a retained ViewModel?

Use ActivityScenario.recreate() in an instrumentation test. This destroys the Activity and recreates it while reusing the ViewModelStore, exactly replicating what DKA does. If any lateinit field is only initialized in a ViewModel-creation callback, the test will fail on assertions that depend on that field. This catches both stale Fragment instance references and uninitialized delegates without needing DKA enabled on a device. Teams shipping Fragment-heavy apps catch recreation regressions earlier by following Android testing discussions on daily.dev.

What causes a memory leak when caching a Fragment instance in a lazy property inside a ViewPager2 FragmentStateAdapter?

FragmentStateAdapter.createFragment() is called every time the adapter needs a Fragment instance, not just once. If the host Fragment caches the returned instance in a lazy property, after Activity recreation the adapter creates a new Fragment while the lazy property still holds the old detached one. The old instance is kept alive through the ViewModelStore GC root, retaining its entire View hierarchy — measured at ~2.3 MB per navigation cycle on a Pixel 6 running API 34. Developers profiling Android memory issues find retention chain analyses like this on daily.dev.

3.3K Impressions