Fehlerbehandlung ist ein harter Brocken und nach meiner Erfahrung macht es kein Entwickler gerne. Trotzdem ist es bei fast jedem neuen Projekt das Letzte, was noch unbedingt getan werden muss. Dabei muss man mit verschiedenen Arten von Fehlern umgehen: nicht funktionierende Netzwerkverbindungen, abgestürzte Server oder benutzerspezifische Validierungsfehler (lokal oder vonseiten des Servers) – um nur ein paar zu nennen. Das Resultat sind umständliche Korrekturprozesse an allen Bestandteilen der App.

Nichtsdestotrotz ist es ein wichtiger, unumgänglicher Prozess. Deshalb hier unser Tipp, um den sauren Apfel zu versüßen:

Unser Setup ist ein ganz gewöhnliches. Für den API-Layer verwenden wir Retrofit mit einem OkHttp-Client, die Daten werden reaktiv über RxJava2 gelesen. Die Architektur unserer Apps entspricht dem MVVM-Model (was hier aber weniger bedeutend ist, da alles mit MVP genauso funktionieren würde). Unser ViewModel beinhaltet eine Möglichkeit zur Ablage. Dieses Repository ist für den Aufruf des Retrofit-API zuständig.

Fehlerbehandlung an einem Beispiel

Zur Anschaulichkeit skizzieren wir hier ein Beispiel auf Basis einer einfachen Login-Anfrage: E-Mail, Passwort, nichts weiter.

Nehmen wir mal an, wir hätten einen Server, der einen 401-Statuscode wegen ungültiger Berechtigung ausgibt, und darüber hinaus einen 400er-Validierungsfehler sowie einen 200er-Statuscode, bei dem alles ok läuft. Die 400er-Anfragen haben Fehlerstellen, die zusätzlich darüber informieren, welches Feld welchen Validierungsfehler aufweist:

An dieser Stelle wollen wir alle möglichen Fehler behandeln, die auftreten können, wenn wir eine Anfrage an das API stellen. Eine allgemeine Fehlerbehandlung für den Login-Screen kann folgendermaßen aussehen:

Puh, das war ganz schön viel Code, der nicht gerade sexy aussieht, nicht?

Zur Klarstellung: In unserem Beispiel scheint die Repository-Ebene vollkommen nutzlos zu sein. Wie sie gleich sehen werden, macht sie in der Praxis aber Sinn.

Festhalten können wir an dieser Stelle auch schon mal: Hübsch ist anders. Stellen Sie sich vor, jede einzelne API-Anfrage an allen Stellen der App, wie oben gezeigt, zu korrigieren. Alter Schwede!

Außer dass es nicht sehr schön ist, ist dieses Vorgehen auch nicht sehr sauber. Die Logik der Fehlerverarbeitung, das Fehler-Body-Format und alle möglichen Situationen, die aus dieser Anfrage entstehen können, gehören in keiner Weise zur View-Ebene.

Aber wo gehören sie hin? Zum ViewModel? Nope. Das ViewModel ist nur eine Verbindung zwischen dem Model (Business-Logik) und der Ansicht. Nein, in unserem Fall wird das Repository zum Modell! Um dieser Logik treu zu bleiben, machen wir alles etwas anders und schöner. Also zurück auf Anfang.

Erstmal führen wir weitere Klassen ein, die mögliche Fehler darstellen können ?:

Dann bewegen wir den Code vom View zum Model:

Wir verwenden den Operator OnErrorResumeNext von RxJava, der die Fehler verschiedenen Streams zuordnet. Wir ordnen es erneut dem Fehlerstream zu, aber mit zugeordneten Retrofit-Exceptions zu unseren Domain-Exceptions. Und tada, die Fehlerbehandlung im ViewModel sieht viel schöner aus:

Beautiful! Ein paar Dinge davon können wir jetzt mit ein wenig Hilfe der Kotlin-Extensions verallgemeinern.

Zum einen muss zum Beispiel die Fehlerzuordnung im Repository immer noch für jede Anfrage individuell durchgeführt werden. Für die sich überschneidenden Teile können wir das aber woanders hin auslagern.

Zum Beispiel durch eine Extension MapApiExceptions auf dem Single:

Dies ermöglicht eine optionale Zuordnungsfunktion der HttpException zu anderen Exceptions. Ist diese Funktion gleich Null werden die Ergebnisse dem GeneralServerException zugeordnet.

Jetzt ändert sich unser Repository-Code folgendermaßen:

Auch der View-Layer kann optimiert werden. GeneralServerException, NoInternetException und UnexpectedException werden wahrscheinlich überall gleich behandelt. Man kann sie also, wie folgt, abstrahieren:

Vergleichen wir unseren Usprungscode mit dem jetzigen, muss man fast lachen. Denn ganz anders als zuvor kann jetzt ganz einfach eine API-Anfrage in einem anderen Teil der App bearbeitet werden.

Eine weitere Verbesserung bestünde nun darin, DomainExceptions automatisch in einen OkHttpInterceptor / Retrofit CallAdapter einzuordnen – aber das wäre schon wieder ein ganz eigener Blog-Artikel. ?

Haftungsausschluss: Die Code-Snippets in diesem Blogbeitrag können als Pseudocode verwendet werden. Es gibt eine Menge Methoden, die für diesen Blog-Beitrag nicht relevant waren und deshalb nicht implementiert wurden.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.