Jetpack Compose

  • Frederick Klyk - adesso mobile
    Teamleiter App-Entwicklung, Software Architekt und Senior Software Engineer

Konzepte, Prinzipien und Aufbau einer Architektur in einem Multi-Module-Projekt

Seit dem 28. Juli 2021 ist das UI Framework Jetpack Compose als stabile Version 1.0.0 verfügbar und wird spätestens seitdem neben Google auch von großen Teilen der Android Developer Community sehr stark beworben. User Interfaces werden mit dem Framework in der Sprache Kotlin geschrieben und im Gegensatz zu den View-basierten XML-Layouts deklarativ umgesetzt.

In der Entwicklung von Android-Apps entstehen durch den Einsatz von Jetpack Compose aufgrund des veränderten Programmierparadigmas sowohl neue Chancen als gleichzeitig auch Herausforderungen und Risiken. In dieser Serie von Blogbeiträgen zum Thema Jetpack-Compose werden vor allem Themen aus der architektonischen sowie konzeptionellen Sichtweise unter die Lupe genommen sowie Best-Practices mit dem Umgang von Composable-Funktionen erläutert. Um einen Einstieg in die Welt des Compose Frameworks zu finden und so ein gemeinsames Verständnis aufzubauen, werden im ersten Teil dieser Jetpack Compose-Serie folgende Fragestellungen behandelt:

  • Welche Gründe sprechen für den Einsatz von Jetpack Compose?
  • Wie kann die Modularisierung in einem Jetpack Compose Projekt umgesetzt werden?
  • Welche Konzepte und Prinzipien sollte man bei dem Einsatz von Jetpack Compose beachten?
  • Welches Architekturmuster wird innerhalb eines Feature Moduls empfohlen und wie sollte es realisiert?

Warum Jetpack Compose?

Jetpack Compose is Android’s modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs. It makes building Android UI faster and easier.”  (Google)

Code Effizient

  • „Für dieselbe Button-Klasse war [der Code] zehnmal kleiner.“ (Twitter)
  • „Außerdem gibt es eine deutliche Reduzierung für jeden Bildschirm, der mit einem RecyclerView gebaut wurde, was die meisten unserer Bildschirme sind.“ (Monzo)
  • „Wir haben uns sehr gefreut, wie wenige Zeilen benötigt werden, um Listen oder Animationen in unserer App zu erstellen. Wir schreiben weniger Codezeilen pro Feature, wodurch wir uns mehr darauf konzentrieren können, unseren Kunden einen Mehrwert zu bieten.“ (Cuvva)

Beispiel: Darstellung und Verwaltung von Listen

Ein eindrucksvolles Beispiel ist die Implementierung einer Liste, die, wie auf dem ersten Bild zu sehen ist, über einen konventionellen RecyclerView Adapter realisiert wurde. In diesem absolut minimalistischen RecyclerView Adapter Beispiel sind die Zeilen Code für das nachträgliche Hinzufügen von Elementen zur Liste, mögliche onClick-Listener sowie die potentielle Nutzung mehrerer ViewTypes, als auch die Optimierung der Listenverwaltung mithilfe von DiffUtil, noch gar nicht mit eingerechnet. Zudem ist für das Befüllen sowie der allgemeinen Interaktion mit den Listenelementen eine Kommunikation über mehrere Klassen und somit Boilerplate Code notwendig.

Darstellung und Verwaltung von Listen, konventioneller RecyclerView Adapter

Dieser komplexe Vorgang wird beim Einsatz mit Jetpack Compose unter anderem durch die Unterstützung vieler „Out-of-the-Box“-Features wie dem onClick-Listener oder der automatischen Erkennung von veränderten Listenelementen mit einem Bruchteil an Codezeilen im Vergleich zum konventionellen RecyclerView Adapter realisiert. Dadurch wird die Komplexität spürbar reduziert und sowohl Lesbarkeit als auch Wartbarkeit und Erwartbarkeit signifikant verbessert.

Jetpack Compose, Darstellung und Verwaltung von Listen

Intuitive und interoperable Entwicklung

  • Layout und Logik werden über die gemeinsame Sprache Kotlin gelöst
  • Gute IDE-Unterstützung, wie z. B. Autovervollständigung sowie fortlaufende Features in der Zukunft
  • Die Compose APIs sind intuitiv: „Wir konnten in einer einzigen Kotlin-Datei das erreichen, was sich sonst über mehrere XML-Dateien erstreckte.“ (Cuvva)
  • Jetpack Compose ist mit dem gesamten vorhandenen Code kompatibel. Die meisten gängigen Bibliotheken wie Jetpack Navigation, ViewModel und Kotlin-Coroutinen funktionieren mit Jetpack Compose
  • Eine Mischung von Compose mit einer View-basierten Benutzeroberfläche (XML) ist möglich

Modularisierung in einem Jetpack Compose-Projekt

Bei der Einführung von Jetpack Compose in einem neuen Projekt kann der Softwarearchitekt auf bewährte Lösungsansätze zurückgreifen und das Gesamtsystem in unterschiedliche Komponenten aufteilen. Die Komponenten sollten selbst wiederum in fachliche und technische Module zerlegt werden. Inwieweit das Projekt horizontal bzw. vertikal geschnitten werden soll, hängt allerdings individuell von den spezifischen Anforderungen des einzelnen Projekts ab. In dem hier schlicht gehaltenen fiktiven Compose-Projekt werden zunächst N Featuremodule angelegt, die jeweils eine Abhängigkeit auf das Designsystem halten. Im Designsystem sollen eigene wiederverwendbare Compose-UI-Elemente hinterlegt werden, wie z. B. Navigation Drawer, Top-App-Bar oder Buttons.

Modularisierung in einem Jetpack Compose-Projekt

Architekturmuster innerhalb eines Feature-Moduls

Innerhalb eines Feature-Moduls kann ebenfalls auf bewährte Muster und Konzepte zurückgegriffen werden, sodass die Verwendung des MVVM-Architekturmusters ratsam ist. Allerdings sollte man bei der Verwendung von Jetpack Compose einige Prinzipien und Regeln beachten.

State Hoisting

Jetpack Compose State Hoisting

Grundsätzlich arbeitet man bei dem Einsatz von Jetpack Compose mit Zuständen und Ereignissen, wobei der State Holder die Zustände verwaltet und die Composable-Funktion für die Darstellung der UI abhängig von dem Zustand das User Interface darstellt sowie den State Holder mithilfe von Ereignissen (z. B. Button-Klick) anspricht.

Die Zustandsverwaltung von einem einfachen UI-Element kann von anderen Composable-Funktionen ebenso gelöst werden wie die Zustandsverwaltung komplexerer UI-Elemente von State holder als Plain-Klassen. Ansonsten werden Android Architecture Components ViewModels (AAC ViewModels) – im weiteren Verlauf als „ViewModel“ bezeichnet – als State holder für den Zugriff von komplexer UI- und Businesslogik als auch für die Zustandsverwaltung der Screens empfohlen. Durch diese Trennung der Verantwortlichkeiten ist die für die Darstellung der UI verantwortliche Composable-Funktion, von der Zustandsspeicherung und -verwaltung entkoppelt.

Dieses Konzept wird als „State Hoisting“ bezeichnet und wird im Kontext von Compose APIs grundlegend verwendet, um eine Composable-Funktion zustandslos zu halten, indem die Zustandsverwaltung zu einem State holder verlagert wird.

Jetpack Compose Lifecycle

ViewModels sollten keine Zustände in Ihrer Signatur verwenden, die in den Composables selbst erstellt worden sind. Der Grund hierfür ist, dass ViewModels z. B. Konfigurationsänderungen überdauern und somit eine längere Lebensdauer besitzen als die Komposition einer Composable-Funktion, was folglich zu Memoryleaks führen kann.

Weiterhin besitzen Composables im Gegensatz zu Activities oder Fragmenten einen anderen Lifecycle. Der Lifecycle einer Composable ist durch drei Ereignisse gekennzeichnet:

Jetpack Compose Lifecycle

Jetpack Compose verwendet Instanzen aus der initialen Komposition, um Veränderungen beobachten und erkennen zu können. Nach der initialen Komposition wird eine neue Komposition von Jetpack Compose immer dann veranlasst, sobald sich der Zustand der App verändert. Dies kann beispielsweise in bestimmten Situationen zu ungewollten Performanz-Einbußen führen, wenn bei häufigen Neuzusammensetzungen der Composable-Funktionen lange und komplexe Berechnungen durchgeführt werden müssen.

Jetpack Compose im Kontext von MVVM

Jetpack Compose im Kontext von MVVM

Google empfiehlt für Composables auf Bildschirmebene die Nutzung von ViewModels als Source of Truth für die Verwaltung des UI-States sowie für den Zugriff auf Daten- und Businessschichten.

Wird das MVVM-Muster verwendet, müssen keine zusätzlichen Anpassungen an den Business Layer vorgenommen werden. Bei der Kommunikation zwischen dem ViewModel und der Composable-Funktion verwendet man für die Zustandsverwaltung vorzugsweise den Observable-Datentyp State und MutableState. Für die konkrete Kategorisierung der Zustände bieten sich Sealed Classes und Enum Classes an.

Codebeispiel Jetpack Compose im Kontext von MVVM

Jetpack Compose Extensions out of the box verfügbar

Compose verfügt über Extensions für das type casting, um die Nutzung von LiveData, (State/Snapshot)Flow oder RxJava2 Observables als State Observable-Datentyp in einer Composable-Funktion zu ermöglichen und den aktuellsten Wert als Zustand in den Composable-Funktionen zu konsumieren. Mit dem Operator by erhält man über Property Delegation einen direkten Zugriff auf den aktuellen Wert von einer State-Variablen. Bei der Nutzung dieser Observable-Typen betont Google das Konvertieren zu einem State<T> Observable, wenn man die Werte in einer Composable-Funktion konsumieren möchte.

Jetpack Compose Extensions

Es wird von Google empfohlen, bei der Kommunikation zwischen ViewModels und Composable-Funktionen weitestgehend mit dem mit dem Datentyp State<T> zu arbeiten und die Erstellung von LiveData oder StateFlow/SharedFlow möglichst zu vermeiden, wenn dies nicht explizit notwendig ist.

Zusammenfassung

Die Entwicklung über deklarative User Interfaces ist eine nicht zu unterschätzende Umstellung für viele Developer. Nach einer moderaten Einarbeitungszeit überwiegen allerdings eindeutig die Vorteile wie eine merkliche Zeitersparnis sowie eine verbesserte Wartbarkeit und Erweiterbarkeit des Quellcodes. Wenn man als Developer die grundlegenden Prinzipien und Regeln von Jetpack Compose wie z. B. den veränderten Lifecycle inklusive des möglichen Impacts durch Neuzusammensetzungen als auch das Konzept des „State Hoisting“ berücksichtigt, ist der Einstieg durch die Verwendung von Kotlin intuitiv und die Lernkurve als durchaus akzeptabel und vertretbar zu bewerten. Weiterhin können bestehende Konzepte und Architekturmuster in großen Teilen wiederverwendet werden, was den Umstieg auf Jetpack Compose sehr angenehm gestaltet.

In einem MVVM-Projekt sollte man weitgehend das ViewModel als Source of Truth State Holder nutzen und somit die überwiegende Logik sowie die Verwaltung von Zuständen im ViewModel halten und Daten von der Business- und Datenschicht unidirektional vom ViewModel zur View bereitstellen.
Wenn andere Datentypen wie z. B. StateFlow nicht benötigt werden, sollte man standardmäßig mit dem Datentyp State<T> arbeiten.

Ausblick

Dieser Blogbeitrag über das UI Framework Jetpack Compose hat ein gemeinsames Verständnis für den empfohlenen Umgang mit den Prinzipien und Konzepten geschaffen und gleichzeitig die Weichen für einen erfolgreichen Start in die Entwicklung gestellt. In den kommenden Blogbeiträgen zum Thema Jetpack Compose werden tiefgreifende und elementare architektonische Themenschwerpunkte sowie verschiedene Best Practices im Umgang mit dem State of the Art UI Framework vorgestellt:

  • Best Practices für
    • den Einsatz von Composable- und Preview Composable-Funktionen
    • die Kommunikation von ViewModels (State Holder) und den Composable-Funktionen
    • die Verwendung von ViewModels in den Composable-Funktionen
    • den Einsatz von zustandslosen (stateless) und zustandsbehafteten (stateful) Composable-Funktionen
  • In welche typische Anti-Pattern der Entwicklung kann man mit Compose geraten und welche zielführenden Lösungsansätze gibt es?
  • Ein effektiver Navigationslösungsansatz über die Navigation Compose Component in einem modularen Jetpack Compose Projekt innerhalb und zwischen den Feature-Modulen
    • Welche Probleme und Konflikte entstehen mit dem Standartansatz?
    • Welcher Lösungsansatz gewährleistet eine reibungslose Navigation?
  • Verwendung von Dependency Injection im Kontext von Jetpack Compose und der Navigation Compose Component

Android 13 – Neuerungen für Entwicklerinnen & Entwickler

Erfahren Sie in unserem Beitrag alles über das neue Update.
Android 13 | Update | Handy

Mehr zu unseren Leistungen

Mehr zum Thema Jetpack Compose

×
Telefon

Sie sind auf der Suche nach einem Experten im Bereich App-Entwicklung? Wir freuen uns auf Ihre Nachricht!

+49 231 99953850
×