Learn Kotlin - Real-world Use Cases & Patterns
Series/Learn Kotlin/Episode 20
Episode 20 of 23

Learn Kotlin - Real-world Use Cases & Patterns

This episode connects all the material with real-world cases: examples of Android apps, backend services, and multiplatform libraries, clean, hexagonal, and reactive architecture, data flow and state management, and scalability and maintainability patterns for systems that last.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

All the material from episodes 0 through 19 now comes together. Episode 20 looks at how those concepts are assembled into real systems: Android apps, backend services, and multiplatform libraries, with clean, hexagonal, and reactive architecture.

Architecture isn't specific code; it's decisions about where code lives and how it communicates. Good architecture lets a system be extended, tested, and maintained for years — the same reason this episode is a bridge toward the closing episode.

After this episode, you'll design clear project structures for different kinds of applications.

Real-world Use Cases

Android App with Layering

Modern Android apps organize code into layers: UI (Compose), state holder (ViewModel), and data (Repository). Data flows in one direction, from the UI to the ViewModel to the Repository and back:

KotlinLapisan pada aplikasi Android
class ProdukViewModel(
    private val repo: ProdukRepository,
) : ViewModel() {
    private val _state = MutableStateFlow<UiState>(UiState.Loading)
    val state: StateFlow<UiState> = _state.asStateFlow()
 
    fun muat() {
        viewModelScope.launch {
            _state.value = UiState.Content(repo.ambilProduk())
        }
    }
}

The ViewModel holds state as a StateFlow and communicates with the Repository. This one-way flow is easy to test: repo can be mocked (episode 13) and the state can be verified without a UI.

Backend Service with the Repository Pattern

A backend service uses the same pattern: a controller or route handles requests, a service holds business logic, and a repository accesses the database. Dependency injection (episode 11) connects these layers without tightly coupled code.

Multiplatform Library

A multiplatform library (episode 12) uses a shared module structure with a small public API and internal implementations. This pattern keeps a stable contract across all platforms while hiding platform-dependent details.

Clean and Hexagonal Architecture

Clean Architecture: Dependencies Point Inward

Clean Architecture centers business logic in a core and makes dependencies point inward: use cases in the middle, frameworks at the edges. In Kotlin, this means a framework-free domain implemented with interfaces:

KotlinPort dan adapter
interface OrderGateway {
    suspend fun simpan(order: Order)
}
 
class OrderService(
    private val gateway: OrderGateway,
) {
    suspend fun buat(order: Order) {
        gateway.simpan(order)
    }
}

OrderService depends on the OrderGateway interface, not a concrete implementation. Implementations (adapters) are injected from outside. The result: business logic can be tested without a database, and frameworks can be swapped without touching the core. You can verify this pattern with ./gradlew test.

Hexagonal Architecture

Hexagonal architecture is a sibling of clean architecture: the business core in the middle, ports (interfaces) at the edges, and adapters for each external technology — database, HTTP, message broker. This pattern enforces clear boundaries and makes external technologies easy to replace.

Reactive and State Management

Reactive Data Flow

Reactive applications propagate data changes automatically. In Kotlin, this means Flow and StateFlow (episode 9): a data source emits values, and the UI or other systems respond:

KotlinAlur data reaktif
val jumlahBaru: Flow<Int> = repo
    .pantauPerubahan()
    .map { it.size }

map { it.size } transforms the data stream without changing its source. Reactive state management keeps the UI always in sync with the data, eliminating the whole class of manual synchronization bugs.

When Reactive Isn't Needed

Reactive architecture adds conceptual complexity. For small applications with linear flows, direct processing is simpler. Start simple and add reactivity only when it's genuinely needed.

Scalability and Maintainability Patterns

Building Systems That Last

Patterns that keep a system healthy as it grows:

  • Clear module boundaries: each module has a single responsibility.
  • An interface for every adapter: external technologies can be replaced.
  • Immutable data: avoids hidden side effects.
  • Tests at every layer: unit, integration, and end-to-end.
  • Consistent naming conventions: names reflect their role in the architecture.

Balancing Simplicity and Scale

The best architecture is one that's sufficient for today's needs and adaptable for tomorrow. Overdoing it adds cost; underdoing it adds debt. Evaluate the architecture regularly and refactor when boundaries start to strain — not earlier, not later.

Closing

Episode 20 connected all the material with the real world: layering in Android and backend applications, clean and hexagonal architecture with ports and adapters, reactive data flow with Flow and StateFlow, and the scalability and maintainability patterns that make systems last.

The key takeaways:

  • Separate UI, state holder, and data layers in Android apps.
  • Interfaces as ports make business logic testable and frameworks replaceable.
  • Clean and hexagonal architecture centralize dependencies in the business core.
  • Flow and StateFlow bring reactive data flow to life.
  • Choose architecture sufficient for today's needs and adaptive for tomorrow.
  • Clear modules, immutable data, and layered tests keep maintainability high.

In episode 21 we'll discuss ecosystem and tools — complete tooling like IntelliJ IDEA, the Kotlin plugin, Gradle, and Android Studio, community resources and learning paths, community channels like Slack and forums, and related frameworks and libraries that extend your abilities.