Structured Concurrency Isn't Just for Coroutines: Why Android's Data Layer Must Stop Launching, Starting, and Broadcasting
For Western Android developers, this analysis exposes that many "best practices" circulating in the community — like Repositories managing their own coroutine scopes — are actually anti-patterns that undermine structured concurrency. The stakes are real: these patterns lead to resource leaks, untraceable bugs, and architectures that resist change. Understanding the ownership principle gives teams a concrete decision framework for code reviews and architecture design.
This analysis argues that many seemingly unrelated Android development debates — whether a Repository should `launch` a coroutine, whether a Data layer should expose `start()/stop()` for resource management, and whether EventBus should still be used — are actually manifestations of a single architectural failure: the ownership of tasks, resources, and events is placed in the wrong layer.
The piece systematically deconstructs three major anti-patterns: ViewModels deciding thread models with `Dispatchers.IO`, UI layers manually catching exceptions, and Repositories secretly creating their own coroutine scopes. It then extends the same structural thinking to continuous resources, showing how `callbackFlow + awaitClose` replaces fragile `start/stop` patterns by binding resource lifecycles to subscription relationships.
Finally, it argues that EventBus and LocalBroadcastManager should be retired entirely, replaced by `StateFlow`, `SharedFlow`, and shared ViewModels — not just for better APIs, but because they restore traceable ownership to inter-component communication. The unifying principle: every task, resource, and event must have a clear, structured owner that manages its lifecycle.
The most common Android coroutine mistakes are not API errors but boundary violations — layers taking on responsibilities that don't belong to them.
The `start/stop` pattern is structurally identical to the old thread model: it relies on human memory for lifecycle management, which is inherently fragile.
Replacing EventBus with Flow-based solutions without also fixing the ownership problem is just swapping tools, not solving the architecture.
The principle of structured ownership applies far beyond coroutines — it's a universal architectural heuristic for any system with tasks, resources, or events.
The fact that many developers instinctively defend Repository-level `launch` as 'simpler' reveals how deeply the non-structured mindset is embedded.
`callbackFlow` is not just syntactic sugar; it's a paradigm shift from imperative lifecycle management to declarative, subscription-driven lifecycle management.
The four-question decision framework for Repository `launch` is a rare example of a concrete, teachable rule that can be applied in code review without ambiguity.
Modern Android architecture's real value isn't in the specific APIs (StateFlow, SharedFlow) but in the structural discipline they enforce.
The article's central claim — that all these problems are the same problem — is a powerful reframing that can simplify team discussions about architecture.
The migration from EventBus to Flow is not just about avoiding deprecated APIs; it's about making communication relationships explicit and traceable.