Learn Jetpack Compose - History, Background & Why Choose Jetpack Compose
Episode 1 of 23

Learn Jetpack Compose - History, Background & Why Choose Jetpack Compose

This episode traces the evolution of Android UI from XML to Compose, the advantages of a declarative approach that is Kotlin-native and reactive, comparisons with cross-platform frameworks, and the modern use cases that benefit most from Compose.

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

Introduction

Before writing a line of Compose code, it's important to understand why Compose exists. Episode 0 prepared your environment; now we step back into the history of Android UI to see the problems Compose is trying to solve.

The old Android approach — XML layouts plus imperative logic in Activities — lasted more than a decade and succeeded in many applications. But its complexity grew out of control: adapters, view holders, and manual state synchronization made bugs easy to slip in. Compose emerged as the answer.

Episode 1 dissects that journey of evolution, Compose's advantages, comparisons with other approaches, and where Compose shines most.

The Evolution of Android UI from XML to Compose

The Imperative Era: XML, findViewById, and Adapters

Android was born with an imperative UI model. You declare layouts in XML files, then wire up the elements with findViewById in your Activity. Every data change must be followed by a manual update to the view — for example, calling notifyDataSetChanged on a RecyclerView adapter.

This model works, but the boilerplate is heavy. The more state a screen has, the more synchronization code you have to maintain. If you forget to update a single view, the UI and the data are no longer in sync.

The Birth of Compose

Jetpack Compose was announced by Google in 2019 and reached stable 1.0 in July 2021. Compose replaces the imperative approach with a declarative one: you describe the UI as a function of state, and the framework takes care of updating it.

KotlinDeclarative UI with Compose
@Composable
fun KartuSalam(nama: String) {
    Card(Modifier.fillMaxWidth()) {
        Column(Modifier.padding(16.dp)) {
            Text("Halo, $nama!")
            Text("Selamat datang di Compose")
        }
    }
}

Card(Modifier.fillMaxWidth()) and Column(Modifier.padding(16.dp)) show the Compose style: every element is a Kotlin function that accepts a modifier. There is no more separate XML file or findViewById — structure, data, and behavior live in one place.

API Stability and Compatibility

Compose ships with a strict compatibility policy. Since version 1.0, Google has committed to keeping APIs stable — breaking changes are always announced in advance and go through a deprecation period before removal. For those starting a new project, this means the Compose code you write today can be maintained for years to come.

Even so, library versions still need to be kept up to date. Compose dependencies are managed through a version catalog at gradle/libs.versions.toml, and you can check for outdated dependencies with Gradle commands. This version consistency matters because Compose, its compiler, and supporting libraries all depend on one another.

Compose's Advantages: Declarative, Kotlin-native, and Reactive

Kotlin-native Without Boilerplate

The entire Compose API consists of Kotlin functions. You use the same language for UI and business logic, with full type safety at compile time. No more R.id lookups, no adapters for simple lists, and previews can be rendered directly in the IDE.

This is what makes onboarding new developers much faster than in the XML era: no more adapter boilerplate and no more manual synchronization between XML and code.

Reactive and State-driven

Compose reads state and updates only the part of the UI that changed. This process is called recomposition: Compose compares the previous composition and re-runs only the composables whose inputs changed. You no longer call manual update functions — just change the state and the UI follows.

This mindset also simplifies testing: because the UI is simply a function of state, screen behavior can be verified without touching an emulator. That testing strategy is covered in full in episode 14, but remember from now on that the declarative, state-driven design exists to make applications easier to verify.

Comparison with XML and Cross-platform Frameworks

Compose vs XML-based UI

XML is still used in legacy applications, and Compose provides a gradual migration path through interop (covered in episode 16). For new projects, Compose wins on productivity because there's less boilerplate and the UI is easier to test.

Compose vs Flutter and React Native

Flutter uses Dart and controls all of its rendering itself, while React Native uses JavaScript with a bridge to native views. Compose is different: it runs directly on the Android framework with native Material components and integrates deeply with systems such as accessibility software and the lifecycle.

Compose's advantage here is native fidelity: there is no separate rendering layer, so visual consistency with the platform and other Android features is higher.

Another advantage is a unified ecosystem: Compose gets updates directly from Google together with the rest of the Android libraries, so features like dynamic color and adaptive layouts reach you without needing third-party libraries. For teams already invested in Kotlin, this reduces both learning cost and maintenance cost.

Choosing Compose isn't a binary decision: you can start with a single screen and expand gradually. Because Compose lives on the same Android framework, this incremental adoption path doesn't force you to rewrite the whole application at once.

Use Cases: When to Use Compose

Compose excels in the following scenarios:

  • Modern Android applications that rely on dynamic UI and reactive data.
  • Reusable design systems: UI components wrapped as composables and used across features.
  • Screens with a lot of state: forms, dashboards, and feeds whose responses change many elements at once.
  • Kotlin teams: because the whole stack is one language, onboarding new members is faster.

For long-running applications with XML UI, Compose can still be adopted incrementally — this hybrid pattern is covered in episode 16. The key is that you don't need to switch all at once.

Starting from a New Project

If you're starting a new project, make sure the first build succeeds from the start:

Verify the first build
./gradlew :app:assembleDebug

Run ./gradlew :app:assembleDebug to make sure all dependencies and plugins are downloaded correctly. If the build succeeds, your foundation is ready for writing your first composable in episode 3.

Closing

Episode 1 explained the historical context of Compose: from the imperative, boilerplate-heavy XML era, a declarative, Kotlin-native, and reactive approach was born. You also saw comparisons with Flutter and React Native, as well as the scenarios where Compose benefits most.

Key takeaways:

  • Old Android used XML plus manual view updates that were prone to bugs.
  • Compose has been stable since July 2021 and replaced that model with a declarative one.
  • Compose is Kotlin-native: one language for UI and business logic.
  • Recomposition updates only the parts of the UI whose state changed.
  • Compose integrates deeply with the Android framework, not as standalone rendering.
  • Adoption in old applications can be gradual through interop.

In episode 2 we will discuss core concepts and Compose architecture — composable functions, recomposition, the composition tree and slots, the rendering pipeline, and how Compose coexists with Android views. This is the conceptual foundation you'll use in every subsequent episode.

Learn Jetpack Compose - History, Background & Why Choose Jetpack Compose | Learn Jetpack Compose