Logo hi.health

hi.health

Startup

Declarative UI on Android

Description

Adrian Tappe von hi.health spricht in seinem devjobs.at TechTalk über deklarative UI on Android und zeigt einige Beispiele für Compose.

By playing the video, you agree to data transfer to YouTube and acknowledge the privacy policy.

Video Summary

In "Declarative UI on Android," Adrian Tappe contrasts the legacy XML-based UI (duplicated state, non–type-safe find-by-ID, lifecycle pitfalls) with Compose’s Kotlin-first, declarative model. Using a simple "High Clickable" demo, he explains composable functions that emit UI, reactive recomposition with remembered UI state, normal Kotlin control flow, and interoperability between Compose and existing XML. He cautions that Compose was still in beta with tight version coupling causing build instability, so teams should plan gradual adoption via interop and wait for stability.

Declarative UI on Android: Moving from XML to Compose – Lessons from “Declarative UI on Android” by Adrian Tappe (hi.health)

What the session made clear

In “Declarative UI on Android,” Adrian Tappe from hi.health offered a straightforward case: the classic XML-centric UI approach on Android is familiar, but it’s time to embrace the declarative paradigm. The talk walked through where the traditional model struggles, how Compose reframes UI as a function of state, what the real-world tooling and versioning caveats look like, and how to adopt it incrementally. From our DevJobs.at editorial seat, the message resonated: a new mindset more than just a new API.

“It’s hard to use that, but it’s time for something new.” The tone was set early: the existing stack works, but Compose is a leap in how we build and reason about UI.

The friction in the XML world

Android teams have long modelled UIs with XML. Tappe’s verdict on learning and adoption was symbolically “four thumbs up,” mainly because “it’s the system we use,” it’s already adopted, and we know how to handle it. Still, his breakdown of the pain points was pointed:

  • Duplicated state: After inflation, the UI holds state. The model holds state too. That duplication forces synchronization, which is error-prone and tedious.
  • Lack of type-safety: You look up elements by ID. That’s not type-safe and invites runtime failures.
  • Lifecycle pitfalls: “On a mobile device, your UI might be gone, and you still try to access it.” Volatile lifecycles make it easy to hit nulls or stale references.
  • Generated helpers only go so far: Modern tools can auto-generate bindings, but as Tappe put it, that can still be “a bit flaky” and brings its own issues.

In short: familiar and widespread, yes; but cumbersome where state and UI intertwine. That’s where Compose changes the calculus.

Compose in practice: a tree of function calls

Tappe illustrated Compose with a small “Hi Clickable” demo. Conceptually: a surface/frame with logo and text; clicking increments a thumbs-up counter until, at five, it shows a star. The specifics of the widget are secondary; the structure is the point:

  • “It’s 100% Kotlin.” Layouts such as Row and Column, widgets like Image and Text—everything is just function calls.
  • You end up with “a tree of function calls.” It reads like nested, scoped calls rather than an external layout description.
  • Code-centric, no context switches: “Yay, no context switches.” No bouncing between Kotlin and XML—one editor, one language.

Why it feels like a DSL—even though it’s “just” Kotlin

The declarative syntax resembles a DSL, powered by two Kotlin/Compose mechanics Tappe highlighted:

  • Higher-order functions and lambdas: A function accepts an anonymous function (lambda) as a parameter to hold child content.
  • Trailing lambda syntax: Kotlin allows the last lambda to be written outside parentheses, which avoids “closing parenthesis hell” when nesting calls.

The result is readable UI code without a separate layout language.

The conceptual shift: functions emit UI

A key insight from Tappe is slightly abstract but central: a Compose function doesn’t “return UI,” it “emits UI while being executed.” The consequences are powerful:

  • If you execute a function (say, for a thumbs-up) twice, it emits two instances of that UI.
  • UI becomes a function of state. “When your state updates … the whole function tree gets re-executed and will emit your new UI.”

This is the declarative core: rather than imperatively mutating a stateful view, changing state re-runs the function and emits the proper UI for the new state.

Local state where it belongs

Compose lets you “remember” state within the function tree for UI-only concerns. Tappe’s example is scroll state—something that doesn’t belong in domain logic but is relevant to the UI. The effect is simpler business logic and a clearer separation of concerns.

Reuse by default: functions are components

In Compose, functions are components by nature. If you have an anonymous function you want to reuse, Tappe notes you can simply “Extract Method/Function” in IntelliJ and you’ve got a component. That reduces friction when modularizing and encourages consistent UI building blocks.

Efficiency via compiler and runtime, not manual caching

The natural worry is performance: “If the whole tree re-executes on each state change, won’t that be expensive?” Tappe answered this with a look at the architecture:

  • Compose ships a Kotlin compiler plugin and a runtime that leverage the fact that “if a function has the same input, it will have the same execution flow and output,” so it doesn’t need to re-execute.
  • In practice, with a large state, only the parts that change are re-executed. Most things don’t.

This is a major win: you write clear declarative code, and the runtime ensures updates are efficient.

Full Kotlin control flow in UI code

Because it’s “100% Kotlin,” you can use language control flow directly:

  • when (akin to switch)
  • repeat loops and other loops

Tappe illustrated it with the emoji demo: if the click count is two, the thumbs-up function runs twice and emits two thumbs.

Tooling and libraries: refactoring, search, and Material as a living example

Compile-time Kotlin for UI also pays off in tooling:

  • IDE support: “Refactoring and search” are “great.” Familiar IntelliJ workflows apply directly in UI code.
  • Standard components as examples: “The whole Material library … is written in Compose.” That makes these components not just building blocks but instructive samples. They’re not opaque tens of thousands of lines—you can read idiomatic Compose in real components.

Interoperability: migrate incrementally

Tappe addressed the crucial strategy question for established apps: “How do we migrate?” Interop is the answer.

  • In an XML-first legacy app, you can drop in a single widget—the “ComposeView”—to start composing functions inside your existing screens. That enables new features to be built declaratively without rewriting everything.
  • Conversely, from a Compose function you can inflate legacy XML, letting you reuse existing components from the old system.

In short: an incremental hybrid is not just possible; it’s designed for.

Status and maturity: still beta, tightly coupled, with real consequences

For all the positives in learning curve and architecture, Tappe was candid about the real-world status at the time of his talk: Compose was “still in beta.” There’s also “tight coupling” between Compose, the Compose compiler, the Kotlin compiler, and the Android Studio preview—because these pieces had to be developed in parallel.

He illustrated the ramifications with specific version experiences:

  • “It didn’t work in Compose Beta 6, didn’t work in 7, [and was] fixed in 8 very quickly.”
  • In their main app: “Our main app didn’t compile in one specific version—which happened to be the one coupled to the Compose version I needed for my example.”
  • “New versions have come out … as of today it works,” yet his assessment remained measured: “Some kind of build instability that I wouldn’t rely on in a productive system.”

This honesty matters: Compose is compelling, but production teams need predictable chains across IDE, compiler, and runtime. Coupling makes those dependencies tangible.

Adoption plan at hi.health: prepare, interoperate, wait for stability

When asked implicitly, “Are we adopting Compose?” Tappe’s answer was clear: “Yes, of course we will—or we’re already preparing the app.” The plan is pragmatic:

  • Design new features with later migration in mind.
  • Use the interoperability to move piece by piece.
  • Wait “until it’s stable”—“hopefully later this year.”

The direction is determined; the exact timeline aligns with tooling stability.

Engineering takeaways we’re carrying forward

From our vantage point, Tappe’s points consolidate into an actionable set of learnings:

  • Declarative beats imperative for UI state: “UI as a function of state” simplifies mental models. No manual syncing of duplicate state, fewer lifecycle traps.
  • One language, one context: All Kotlin means no context switching between XML and code.
  • Real reuse: Functions are components. “Extract Method” often suffices to carve out shareable building blocks.
  • Efficiency through the runtime: The Compose compiler/runtime pairing ensures that unchanged parts don’t re-execute.
  • Pragmatic migration: ComposeView and the ability to inflate XML from Compose make incremental adoption practical.
  • Be mindful of tooling maturity: With beta status and tight coupling, version edges are a reality.

The “Hi Clickable” demo as a compact blueprint

While Tappe didn’t show full code, the semantics were clear. A small, interactive unit with logo and text, counting thumbs on click and showing a star at five, demonstrates the essence:

  • State change (click increments) triggers re-execution of the function tree.
  • Kotlin control flow decides whether to emit thumbs or a star.
  • Repetition (e.g., via repeat) emits multiple instances (“two clicks—two thumbs”).

The simplicity is deliberate: understand it in a tiny component, and you can scale it up to full screens.

Consequences for teams and codebases

Our reading is that moving to Compose is not just an API delta; it reshapes architecture and team workflows. Based on Tappe’s statements, and without going beyond the transcript:

  • Keep UI-only state local: Scroll position and similar concerns belong in the UI tree, not in domain models, which keeps business logic lean.
  • Define domain state cleanly: The clearer your state, the easier it is to live “UI as a function of state.”
  • Manage versions deliberately: Early adopters must align Compose, the Kotlin compiler, and Android Studio preview versions—with the risk of hitting bad combinations.
  • Default to incremental migration: Interop is not a luxury, it’s the plan—compose new elements while legacy XML drives the remainder until you’re ready.

Developer experience and design collaboration

Tappe emphasized how pleasant the day-to-day feels: “Code-centric, yay, no context switches.” Scoped function calls, natural syntax, and strong IDE support for refactoring and search add up to a smoother workflow. He even joked about “making our designer happy very soon,” hinting at the improvements in iteration speed and clarity.

The fact that Material components are written in Compose makes them not just usable, but readable as idiomatic examples—great for teams learning by dissecting real components.

Call it what it is: beta

Perhaps the most important management-level takeaway from Tappe’s talk is a sober status check. Compose is convincing—in learning, expressiveness, and architecture. But “still in beta,” tightly coupled, and with lived examples of version friction across Compose, the Kotlin compiler, and the IDE. That’s why the hi.health adoption plan rings true: prepare now, interoperate, and wait for stability for full production reliance.

Resources and what to read next

Tappe closed with a practical nudge: “Please look at the resources.” He pointed to “great talks from Google I/O” and “really great documentation.” If you’re internalizing the declarative paradigm, those are natural next reads.

From the DevJobs.at editorial perspective, the path is clear: if you build or renovate Android UI today, Compose is the trajectory. Interop makes the transition manageable. The right timing depends on your risk tolerance and the stability of the toolchain.

Summary: our key takeaways

  • Compose reframes Android UI as “UI as a function of state,” reducing synchronization work and clarifying architecture.
  • All Kotlin: no XML, no context switching—just function trees with natural syntax (thanks to lambdas and trailing-lambda syntax).
  • Compiler/runtime optimizations avoid unnecessary re-execution—declarative code stays efficient.
  • Interoperability supports incremental migration—use ComposeView in XML, or inflate XML from Compose as needed.
  • Status at the time of the talk: “still in beta,” tightly coupled, with real-world build instability across Compose/Kotlin/IDE versions.
  • Adoption plan at hi.health: yes, but gradually, leveraging interop and waiting for stability “hopefully later this year.”
  • The “Hi Clickable” demo distills the model: state, control flow, and emission working together in a small component.

“Declarative UI on Android” by Adrian Tappe (hi.health) leaves us with a pragmatic blueprint: refocus on state, embrace the function-tree mental model, use interop to migrate safely, and keep a close eye on version alignment until the toolchain settles.

More Tech Lead Stories