Navigation und API

40 MinutenEine Liste anzeigen, Screens verbinden und echte Daten mit einem Coding Agent anbinden

Listen mit LazyColumn

LazyColumn zeichnet nur die sichtbaren Einträge. Damit bleiben auch lange Listen performant.

LazyColumn(contentPadding = innerPadding) {
    items(cities, key = { it.name }) { city ->
        ListItem(
            headlineContent = { Text(text = city.name) },
            modifier = Modifier.clickable { onCityClick(city) },
        )
        HorizontalDivider()
    }
}

Navigation 3

Der Back Stack ist eine Liste, die die App selbst als State hält.

  • Navigieren: Element hinzufügen, backStack.add(…)
  • Zurück: letztes Element entfernen, backStack.removeLastOrNull()
  • Angezeigt wird immer das letzte Element der Liste

Routen als Typen

Jeder Screen ist ein Typ, Argumente sind Properties.

@Serializable
data object CityList : NavKey

@Serializable
data class CityWeather(val city: City) : NavKey

NavDisplay

NavDisplay zeigt das letzte Element des Back Stacks an.

val backStack = rememberNavBackStack(CityList)

NavDisplay(
    backStack = backStack,
    onBack = { backStack.removeLastOrNull() },
    entryProvider = entryProvider {
        entry<CityList> { CityListScreen(/* ... */) }
        entry<CityWeather> { key ->
            WeatherScreen(city = key.city, /* ... */)
        }
    },
)

API-Anbindung mit einem Coding Agent

Aufgabe für den Agent: ein ApiWeatherRepository für Open-Meteo bauen und im ViewModel einsetzen.

  • Plan anfordern und prüfen, bevor Code entsteht
  • umsetzen lassen, bauen, App testen
  • Diff lesen und erklären können
  • erst dann committen

Ktor-Client

Ktor ist ein HTTP-Client von JetBrains für Kotlin. Der Request steht explizit im Code.

override suspend fun getCurrentWeather(city: City): CurrentWeather {
    val url = "https://api.open-meteo.com/v1/forecast"
    val response: ForecastResponse = client.get(url) {
        parameter("latitude", city.latitude)
        parameter("longitude", city.longitude)
        parameter("current", "temperature_2m,weather_code,wind_speed_10m")
        parameter("timezone", "auto")
    }.body()
    return response.current.toCurrentWeather()
}

DTO und Datenmodell

Die JSON-Antwort bekommt eigene Klassen (DTOs). Sie werden auf unser Datenmodell abgebildet.

@Serializable
data class CurrentDto(
    @SerialName("temperature_2m") val temperature: Double,
    @SerialName("weather_code") val weatherCode: Int,
    @SerialName("wind_speed_10m") val windSpeed: Double,
)

Der Nutzen der Architektur

Für echte Daten wurde kein einziges Composable geändert.

$ git diff --stat schritt-08 schritt-09
 app/build.gradle.kts                    |  4 ++
 app/src/main/AndroidManifest.xml        |  2 +
 .../data/ApiWeatherRepository.kt        | 45 +++++++++
 .../data/api/HttpClient.kt              | 15 ++++
 .../data/api/OpenMeteoDto.kt            | 18 ++++
 .../ui/WeatherViewModel.kt              |  4 +-
 gradle/libs.versions.toml               |  5 ++

Übungen

Selbst oder mit einem Coding Agent umsetzen, jeweils Plan und Diff prüfen.

  • Stadtsuche über die Geocoding-API von Open-Meteo
  • 7-Tage-Vorhersage im Wetter-Screen
  • Zurück-Pfeil in der TopAppBar
  • Button „Erneut versuchen“ bei Fehlern
  • Favoriten dauerhaft speichern (DataStore oder Room)
  • Unit-Tests für das ViewModel mit FakeWeatherRepository