Android Coden
Android 7 min lesen

REST-API-Konzepte für Android

REST-APIs strukturieren mobile Backend-Zugriffe über Ressourcen, Endpunkte und Verben. Du lernst, wie du sie in Android sauber einordnest.

Wenn deine Android-App Daten von einem Server lädt, brauchst du ein klares Modell dafür, was genau angefragt, verändert oder gelöscht wird. REST-API-Konzepte helfen dir dabei: Du denkst nicht zuerst in Screens oder Buttons, sondern in Ressourcen, Endpunkten und Verben. Dieses Denken ist wichtig, weil es deine Repository-Klassen, Fehlerbehandlung, Tests und später auch Offline-first-Synchronisation deutlich verständlicher macht.

Was ist das?

REST steht für einen Stil, mit dem Backends ihre Daten über HTTP bereitstellen. Für dich als Android-Entwickler ist nicht jede theoretische REST-Regel gleich wichtig. Entscheidend ist das Grundmodell: Eine API stellt Ressourcen bereit, diese Ressourcen sind über Endpunkte erreichbar, und Aktionen werden über HTTP-Verben ausgedrückt.

Eine Ressource ist ein fachliches Objekt oder eine Sammlung solcher Objekte. In einer Lern-App könnten das courses, lessons oder progress sein. Ein Endpunkt ist die konkrete Adresse, über die du eine Ressource ansprichst, zum Beispiel /courses/42. Ein Verb beschreibt, was du tun möchtest: GET liest Daten, POST legt meist etwas an, PUT oder PATCH ändern Daten, DELETE entfernt etwas.

Für Anfänger ist dieses Modell wertvoll, weil es Ordnung in Netzwerkcode bringt. Du siehst nicht nur eine URL und irgendein JSON, sondern erkennst eine fachliche Absicht. GET /courses bedeutet: Die App fragt eine Liste von Kursen ab. PATCH /progress/42 bedeutet: Ein vorhandener Fortschritt wird teilweise geändert. Diese Lesbarkeit hilft dir im Alltag beim Debuggen, beim Austausch mit Backend-Teams und beim Schreiben von Tests.

Im modernen Android-Kontext gehört REST-Kommunikation in die Data Layer. Deine Compose-Oberfläche sollte nicht wissen, welche URL aufgerufen wird. Auch ein ViewModel sollte nicht mit Roh-Endpunkten arbeiten müssen. Typisch ist eine Struktur, bei der ein API-Service die HTTP-Details kapselt, ein Repository diese Daten fachlich aufbereitet und die UI nur Zustände wie „lädt“, „Daten vorhanden“ oder „Fehler“ beobachtet.

Wie funktioniert es?

Eine REST-API nutzt HTTP als Transport und gibt häufig JSON zurück. Aus Android-Sicht besteht ein Aufruf aus mehreren Schichten. Zuerst gibt es die Request-Informationen: Methode, URL, Header, Query-Parameter und optional einen Body. Danach kommt die Response mit Statuscode, Headern und Antwortkörper. Deine App muss beides sinnvoll interpretieren.

Die wichtigsten Begriffe sind dabei Ressourcen, Endpunkte und Verben. Ressourcen sollten möglichst stabil und fachlich benannt sein. Ein Endpunkt wie /users/17/orders ist meist verständlicher als /getOrdersForUser?id=17, weil die Struktur zeigt, dass Bestellungen zu einem Nutzer gehören. Verben sollten nicht als künstliche Wörter in der URL stecken, wenn HTTP sie bereits ausdrücken kann. Statt /deleteCourse/42 ist DELETE /courses/42 klarer.

Statuscodes sind ein weiterer Teil des Konzepts. 200 oder 204 zeigen erfolgreiche Antworten, 201 passt häufig zu neu angelegten Ressourcen. 400 deutet auf fehlerhafte Eingaben hin, 401 auf fehlende Authentifizierung, 403 auf fehlende Berechtigung, 404 auf eine nicht gefundene Ressource. 500 steht für Serverprobleme. Für Android-Code ist wichtig: Ein HTTP-Fehler ist nicht dasselbe wie ein Verbindungsfehler. Ein 404 kommt vom Server, ein Timeout entsteht meist schon auf Netzwerkebene.

In einer sauberen Architektur trennst du API-Modelle von Domain-Modellen. Das JSON des Backends kann Felder enthalten, die deine App nicht direkt zeigen sollte. Umgekehrt kann deine UI Daten benötigen, die aus mehreren Antworten zusammengesetzt werden. Deshalb mappen viele Apps DTOs aus der Netzwerkebene in eigene Modelle. So bleibt deine App stabiler, wenn das Backend ein zusätzliches Feld liefert oder eine interne Bezeichnung verwendet.

Offline-first macht REST nicht überflüssig, sondern verändert die Rolle der API. Die App liest idealerweise aus einer lokalen Quelle, etwa einer Datenbank, und synchronisiert im Hintergrund mit dem Backend. Ein REST-Endpunkt liefert dann neue Daten, bestätigt Änderungen oder meldet Konflikte. Das Repository entscheidet, wann lokale Daten aktualisiert werden und wie die UI davon erfährt. Genau deshalb ist das Ressourcenmodell so wichtig: Wenn du nicht weißt, welche Ressource geändert wurde, kannst du sie auch nicht sauber cachen oder synchronisieren.

Ein häufiger Denkfehler ist, REST-Endpunkte an Screens auszurichten. Ein Backend-Endpunkt wie /homeScreenData kann kurzfristig bequem wirken, koppelt deine App aber stark an eine bestimmte Oberfläche. Fachliche Ressourcen wie /courses, /recommendations und /progress sind meist langlebiger. Die UI kann daraus verschiedene Screens bauen, während die Data Layer weiterhin mit klaren Objekten arbeitet.

In der Praxis

Stell dir vor, du baust eine App, die Kurse anzeigt und den Lernfortschritt speichert. Ein mögliches REST-Modell könnte so aussehen:

  • GET /courses lädt alle sichtbaren Kurse.
  • GET /courses/{id} lädt Details zu einem Kurs.
  • GET /courses/{id}/lessons lädt Lektionen eines Kurses.
  • PATCH /progress/{lessonId} aktualisiert den Fortschritt einer Lektion.
  • POST /sync/events sendet lokal gesammelte Änderungen an den Server.

In Kotlin würdest du diese Endpunkte meist hinter einem Interface kapseln. Das folgende Beispiel ist bewusst klein gehalten. Es zeigt nicht jede Retrofit-Option, sondern die Rollen der Methoden:

interface CourseApi {
    @GET("courses")
    suspend fun getCourses(): List<CourseDto>

    @GET("courses/{id}")
    suspend fun getCourse(
        @Path("id") id: String
    ): CourseDto

    @GET("courses/{id}/lessons")
    suspend fun getLessons(
        @Path("id") courseId: String
    ): List<LessonDto>

    @PATCH("progress/{lessonId}")
    suspend fun updateProgress(
        @Path("lessonId") lessonId: String,
        @Body request: ProgressUpdateDto
    ): ProgressDto
}

class CourseRepository(
    private val api: CourseApi,
    private val dao: CourseDao
) {
    suspend fun refreshCourses() {
        val remoteCourses = api.getCourses()
        val entities = remoteCourses.map { it.toEntity() }
        dao.upsertAll(entities)
    }

    fun observeCourses(): Flow<List<Course>> {
        return dao.observeAll()
            .map { entities -> entities.map { it.toDomain() } }
    }

    suspend fun markLessonDone(lessonId: String) {
        val response = api.updateProgress(
            lessonId = lessonId,
            request = ProgressUpdateDto(done = true)
        )
        dao.upsertProgress(response.toEntity())
    }
}

Das Beispiel zeigt eine wichtige Regel: Die UI ruft nicht GET /courses auf. Sie fragt das Repository nach Kursen. Das Repository entscheidet, ob es Daten aus dem Netzwerk holt, aus der Datenbank liest oder beides kombiniert. Für Compose bedeutet das: Deine Composables bleiben auf UI-Zustand fokussiert. Sie zeigen Listen, Ladeindikatoren und Fehlermeldungen, kennen aber keine Endpunktnamen.

Eine praktische Entscheidungsregel lautet: Benenne Endpunkte nach Ressourcen, nicht nach UI-Aktionen. Wenn du einen Button „Kurs starten“ hast, ist der passende API-Aufruf nicht automatisch /startCourse. Vielleicht erzeugt die App einen Fortschrittseintrag mit POST /progress, vielleicht ändert sie den Status mit PATCH /enrollments/{id}. Die richtige Ressource hängt davon ab, welches fachliche Objekt sich auf dem Server verändert.

Eine typische Stolperfalle ist der falsche Einsatz von POST, weil es „irgendwie funktioniert“. Wenn jede Änderung als POST /doSomething umgesetzt wird, verlierst du viel Bedeutung. Caching, Wiederholbarkeit und Fehleranalyse werden schwieriger. GET sollte keine Daten verändern. DELETE sollte klar machen, welche Ressource entfernt wird. PATCH passt gut, wenn nur einzelne Felder geändert werden. Diese Unterscheidung ist kein Formalismus, sondern macht Verhalten nachvollziehbar.

Eine weitere Stolperfalle liegt in IDs und Pfaden. Wenn deine App mit lokalen Entwürfen arbeitet, etwa im Offline-first-Modus, kann es vorübergehend lokale IDs geben, die der Server noch nicht kennt. Dann darfst du nicht blind PATCH /progress/local-123 senden, wenn der Server nur seine eigenen IDs versteht. Das Repository muss solche Zustände abbilden: lokal gespeichert, noch nicht synchronisiert, vom Server bestätigt oder wegen Konflikt abgelehnt.

Auch Fehlerbehandlung sollte zum REST-Modell passen. Ein 401 sollte meist zur erneuten Anmeldung oder Token-Erneuerung führen. Ein 404 bei GET /courses/42 kann bedeuten, dass der Kurs gelöscht oder für diesen Nutzer nicht verfügbar ist. Ein Timeout sollte nicht so behandelt werden, als sei die Ressource leer. Besonders bei Offline-first-Apps ist das wichtig: Wenn das Netzwerk ausfällt, kannst du oft weiterhin lokale Daten anzeigen und die Synchronisation später erneut versuchen.

Zum Üben kannst du eine kleine Tabelle für eine vorhandene App schreiben: Welche Ressourcen gibt es? Welche Endpunkte greifen darauf zu? Welche Verben werden verwendet? Danach prüfst du im Code, ob diese Struktur in deinen Repository-Methoden sichtbar bleibt. Wenn deine Methode loadHomeStuff() heißt und drei unklare Endpunkte mischt, ist das ein Hinweis auf fehlende fachliche Trennung. Wenn Methoden wie refreshCourses(), observeCourses() und markLessonDone() existieren, ist die Absicht besser erkennbar.

Beim Testen kannst du REST-Konzepte ebenfalls konkret prüfen. Unit-Tests für Mapper stellen sicher, dass DTOs korrekt in Domain-Modelle übersetzt werden. Repository-Tests prüfen, ob bei erfolgreichem GET die lokale Datenbank aktualisiert wird. Tests für Fehlerfälle prüfen, ob ein 404 anders behandelt wird als ein Timeout. In Code-Reviews solltest du besonders auf drei Fragen achten: Ist die Ressource klar? Passt das HTTP-Verb zur Aktion? Bleiben API-Details außerhalb der UI?

Fazit

REST-API-Konzepte geben dir eine klare Sprache für mobile Backend-Zugriffe: Ressourcen beschreiben, worum es fachlich geht, Endpunkte machen diese Ressourcen erreichbar, und Verben drücken die Aktion aus. Für Android ist das vor allem in der Data Layer wichtig, weil dort Netzwerk, lokale Speicherung und Synchronisation zusammenlaufen. Prüfe dein Verständnis aktiv, indem du bestehende API-Aufrufe nach Ressourcen und Verben sortierst, einen Repository-Test für einen Fehlerfall schreibst und im Debugger beobachtest, welche Response deine App wirklich verarbeitet.

Quellen (2)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.