SharingStarted Is the Real Engine Behind Kotlin's stateIn
Misconfiguring `SharingStarted` causes silent bugs: duplicate network requests, stale data flashes, and resource leaks after a screen closes. Picking the right strategy and tuning its two timeout parameters eliminates entire categories of lifecycle-related defects in Android apps.
Most Android developers treat `stateIn()` as a boilerplate one-liner, but the `SharingStarted` parameter does the heavy lifting. It turns a cold Flow into a hot one and dictates the entire upstream lifecycle: startup trigger, shutdown timing, and cache invalidation. The three built-in strategies—`Eagerly`, `Lazily`, and `WhileSubscribed`—map directly to global data, lazy caches, and UI-bound state.
Two overlooked parameters on `WhileSubscribed` prevent unnecessary work during configuration changes and control whether stale data is replayed. A `stopTimeoutMillis` of 5000 acts as a debounce that keeps the upstream alive across screen rotations, avoiding redundant network calls. Setting `replayExpirationMillis` to zero flushes the cache immediately when subscribers drop, which is essential for real-time data where old values are misleading.
The practical upshot is a set of clear defaults: a 5-second timeout for standard UI state, zero replay expiration for live feeds like stock prices, and `Eagerly` with an application-scoped coroutine for login state or config that must be ready before any UI appears.
The `WhileSubscribed` timeout is effectively a debounce mechanism for the upstream lifecycle, not just a cleanup delay.
Using `replayExpirationMillis = 0` turns `stateIn` into a strict request-on-subscribe pattern that prevents the flash of stale content users often see when returning to a screen.
The mental model shift from 'stateIn is a caching operator' to 'stateIn is a lifecycle manager with a caching side effect' changes how developers reason about resource cleanup.