This episode ties everything together in the real world: case studies of social, e-commerce, and dashboard applications, MVI and MVVM architecture patterns with unidirectional data flow, scaling with modules, and a maintainable design system.

All the previous material feels disconnected: state, navigation, persistence, testing, performance. Episode 20 weaves it all into the real patterns production teams use in social, e-commerce, and dashboard applications.
In the real world, no single pattern answers everything. What you need is architectural understanding: how data flows, how state is shared, and how the application grows without becoming chaotic.
Episode 20 covers case studies, architecture patterns, module scaling, and a maintained design system.
Social applications center on feeds and notifications: LazyColumn with Paging (episode 9), interaction state like likes and comments hoisted into the ViewModel, and animations for instant feedback. Unidirectional data flow helps a lot here because many states affect each other.
E-commerce is rich in forms and carts: navigation with arguments and deep links (episode 8), offline persistence for the cart (episode 11), input validation (episode 7), and consistent theming for branding (episode 6).
Dashboards need real-time data and adaptive layouts: BoxWithConstraints for wide screens, periodic refresh with coroutines (episode 10), and custom charts using custom layouts (episode 18). Dashboards are also a strong candidate for Compose Multiplatform from episode 17.
All modern Compose architecture patterns hold the same principle: data flows in one direction. The UI sends intents, the ViewModel processes them, and new state flows back to the UI:
UI --> Intent --> ViewModel --> State --> UIThe one-way flow makes the application easy to trace: every state change has a clear cause. This avoids bugs born from many overlapping state update paths.
MVI expresses the entire screen condition in a single state. Combine the sealed UiState pattern from episode 10 and intents as events:
sealed interface PencarianIntent {
data class UbahQuery(val teks: String) : PencarianIntent
data object Cari : PencarianIntent
}
data class PencarianUiState(
val query: String = "",
val hasil: List<Hasil> = emptyList(),
val memuat: Boolean = false
)PencarianUiState is the single source of truth for the screen, and PencarianIntent is the only path for changes. The UI renders state; the ViewModel receives intents and produces new state.
MVVM is lighter than MVI: the ViewModel exposes state through StateFlow, and the UI uses collectAsStateWithLifecycle (episode 10). Choose MVVM when the team is smaller, and MVI when the application is complex and needs a strict change trail.
A quick way to assess pattern fit: first write down the most complex data flow scenario in the application. If the flow is linear and few, MVVM is enough. If many events affect each other and an audit trail is needed, MVI gives a clearer picture. Both remain testable without the UI — run ./gradlew :app:testDebugUnitTest to verify state logic every time the pattern changes.
As features grow, split them into Gradle modules: :core:designsystem, :core:network, :feature:auth, :feature:keranjang. Each module has clear boundaries and dependencies:
dependencies {
implementation(project(":core:designsystem"))
implementation(project(":core:data"))
}implementation(project(":core:designsystem")) makes a feature module depend on the shared design system. Modularization cuts build time and enforces healthy architectural boundaries.
Combine modularization with modular navigation from episode 8: each module registers its routes via a NavGraphBuilder extension. The root app only assembles the graph. The result: features can be developed and tested separately.
One thing to remember: an architecture pattern is not an end goal, but a tool to keep the application easy to change. Every decision — MVI, MVVM, modularization, or the number of modules — should be measured by how easily the team adds features and fixes bugs. If a layer only adds boilerplate without reducing the cost of change, reconsider it.
A design system is its own module containing theme tokens, UI primitives, and shared components. Every feature uses it, rather than defining its own colors. Every visual change needs to happen only once.
Treat the design system like a library: versioned with a changelog. Breaking changes are published before feature modules adopt them. With clear contracts, feature teams can move fast without breaking each other.
Episode 20 wove the material into real patterns: social, e-commerce, and dashboard case studies, MVI and MVVM architectures with unidirectional data flow, scaling applications through Gradle modularization and per-module navigation, and a design system as a module with clear contracts.
Key takeaways:
In episode 21 we will discuss ecosystem and tools — Android Studio, Compose Preview, and DevTools, libraries like Accompanist, Coil, Retrofit, and Hilt, community resources, and how to keep up with Compose development.