Traditionelle Web- und Desktop-Anwendungen werden oft mit Hilfe von MVC-Frameworks erstellt. Bei Android macht es Sinn, weitgehend bei den Bordmitteln zu bleiben, denn dadurch bleibt die App schlank und leichtgewichtig. Es ist trotzdem möglich die Anwendung nach dem MVC-Muster zu strukturieren. Wir wollen hier unsere Methode an Hand des Dashboard aus Hannibal Mobile vorstellen.

Hannbibal Dashboard mit Gauges

 

Controller und View

Was Android mitbringt

Eine wichtige Komponente für eine App in Android ist die Activity. Activities bestehen aus einer ContentView sowie mehreren Callbacks, welche ausgeführt werden, wenn die Activity gestartet, pausiert, fortgesetzt oder zerstört wird. Die ContentView einer Activity ist üblicherweise ein Layout, welches aus View-Objekten, wie etwa Input Felder, Labels, etc. besteht. Eine Activity übernimmt damit Aufgaben von View und Controller: Es nimmt Benutzerinteraktionen entgegen, verarbeitet sie, aber ist auch für die Aktuallisierung der Präsentation zuständig.

Seit Android 3.0 gibt es die Möglichkeit Activities in sog. Fragmente zu untergliedern. Mit der Support-Library werden Fragmente auch für frühere Android Versionen bereit gestellt. Fragmente haben genau wie auch Activities, einen Lebenszyklus und haben optional einen ContentView.

Um View und Controller Logik möglichst zu trennen, bauen wir unseren MVC View bei Hannibal Mobile aus einem Fragment auf und lassen die Activity als Controller fungieren. Die Activity besitzy zwar immer noch ein Layout, dieses beinhaltet jedoch nur ein Fragment, unseren View.

Im Layout ist bereits die Fragment-Klasse verknüpft, damit wird sie automatisch instanziiert und dem Dashboard hinzugefügt. Die Activity braucht dieses Layout lediglich zu setzen:

Auf die fehlende Methode startGaugeLoaders kommen wir später zurück.

Was uns bei Android fehlt

Damit der Controller auf Ereignisse des View, beispielsweise Klicks eines Buttons, reagieren kann, ohne dass diese beiden Komponenten fest verdrahtet werden, bietet sich ein Observer-Muster an. Hierfür ist ein Event-Bus sinnvoll. Damit sind View und Controller nur noch lose gekoppelt und wir brauchen auch nicht für jedes View-Ereignis ein eigenes Listener-Interface bereitstellen, wie es in Android typisch wäre, was unseren Code zudem schlanker macht.

Ausserdem fehlt uns eine Art Dependency-Injection zum Autowiring von View-Komponenten und anderen Objekten auf Membervariablen. Diesen beiden Anforderungen können wir leicht gerecht werden, in dem wir RoboGuice verwenden. Erwähnenswerte Alternativen zum Event-Bus wären beispielsweise Otto, zur Dependency Injection Dagger oder Butterknife.

Die Funktionsweise des Event-Bus und die weiteren Features von RoboGuice erläutere ich anhand von Anmerkungen zu den folgenden Codeausschnitten.

Das Layout für unser Dashboard Fragment (unserem View) besteht lediglich aus einer GridView:

Dank Roboguice, können wir unser Fragment (unseren View) relativ einfach gestalten:

Anmerkungen:

  • Mit @InjectView wird RoboGuice instruiert, die GridView in Membervariable zu injizieren
  • Der EventManager von RoboGuice ist unser EventBus, wir bekommen die Instanz, die zur jeweiligen Activity gültig ist, mit @Inject
  • Die einzelnen Gauges werden in der Methode createDashboardAdapter() erzeugt. Dies sind relativ komplexe Komponenten mit eigenem Layout und werden daher in einem zukünftigen Blogpost beschrieben.
  • Der AdapterView.OnItemClickListener erzeugt ein OnGaugeClickEvent und schickt es über den EventBus

OnGaugeClickEvent ist dabei ein einfaches POJO:

Wie bereits erwähnt, schickt das Fragment eine Instanz dieses OnGaugeClickedEvent über den EventBus, sobald ein Gauge angeklickt wurde. Auf dieses Event können wir nun in unserer Activity sehr einfach reagieren:

Nun haben wir ein Dashboard mit klickbaren Gauges. Es es ist aber immer noch unklar wie wir nun die Daten von Hannibal in unsere Gauges kommen.

Model

Hannibal ist ursprünglich eine Web-Applikation. Wir haben das vorhandene REST-API etwas erweitert, um alle nötigen Daten (z.B. Tabellennamen) auch in der Mobile App zur Verfügung zu haben. Das REST-API liefert JSON-Objekte, welche wir auf Java-POJOs mappen. Die Logik zum Laden der Objekte kapseln wir wiederrum in einem Data Access Object.

Da HTTP-Aufrufe unter Umständen sehr lange dauern, dürfen wir das UI nicht blockieren. Seit Android 3 gibt es die Möglichkeit, den LoaderManagerzu verwenden, welcher es sehr einfach macht, mit asynchronen Prozessen zu arbeiten. Mit der Support-Library wird der LoaderManager auch für frühere Android Versionen bereit gestellt. Wir haben die Loader so implementiert, dass sie nach dem Laden Events über den EventManager schicken. Damit kann jede Komponente (und vor allem: die Fragmente, unsere Views) in der Applikation auf neue Daten reagieren.

Wir können unser DashboardFragment nun erweitern, um über die Daten benachrichtigt zu werden und die Präsentation zu aktualisieren.

Anmerkungen:

  • Durch @Observes können wir uns beim EventManager für ein Event anmelden
  • Da die einzelnen Gauges relativ komplex sind, delegiert das Fragment, das aktuallisieren der Präsentaiton an die einzelnen Gauges, in dem hier lediglich die Setter aufgerufen werden.

Nun müssen wir die Loader nur noch starten, hierzu fügen wir der DashboardActivity die Methode startGaugeLoaders hinzu:

Anmerkungen:

  • In Hannibal wird bei einem Fehler eine separate Activity gestartet, daher wird der Fehler in der Activity behandelt. Je nach dem kann es auch sinnvoll sein, in dem Fragment auf den Fehler zu reagieren.
  • Die Implementierung der einzelnen Loader samt Caching und DAO würde den Rahmen dieses Blogpost sprengen. Informationen zur Implementierung von Loadern stehen im Developer Guide.

Resultat

Schlussendlich ergibt sich nun folgendes Bild:

Hannibal Mobile Architecture

Vergleicht man dies mit dem klassischen Schaubild zum MVC-Muster, kristallisiert sich schnell heraus, dass unser Loader und Model also den Model-Part des MVC-Pattern bilden.

Der Ablauf beim Start der Activity ist folgender:

  1. Die Activity weist die Loader an Daten zu laden

  2. Die Loader laden die Daten via DAO oder holen sie aus dem Cache

  3. Der Loader schickt die Nachricht über den Event-Bus an das Fragment

  4. Das Fragment nimmt die Daten entgegen und aktualisiert die Repräsentation

 Bei einer Interaktion des Benutzers, sieht der Ablauf nun typischerweise so aus:

  1. Das Fragment benachrichtigt die Activity über den EventBus

  2. Die Activity reagiert auf die Nachricht und weisst ggf. die Loader an neue Daten zu laden und/oder zu manipulieren (letzteres gibt es in Hannibal nicht)

  3. Die Loader benachrichtigen ggf. das Fragment über Änderungen am Model über den Event-Bus

Damit verwenden wir Fragments entgegen der Android-Definition bewusst nur für die Präsentation und nicht auch für das Verhalten.

Andere Konzepte

  • Inspiriert wurde unser Ansatz primär von der Blog-Reihe von Josh zum Thema Android Architecture. Sein Ansatz verwendet keine Fragmente, sondern verknüpft das Layout einer Activity direkt mit einer separaten View-Klasse. Er setzt auch kein Framework wie RoboGuice ein.

  • Ein anderer interessanterer Ansatz ist die Verwendung des Musters Presentation Model bzw. Model View ViewModel. Hierfür gibt es Frameworks wie Robo Binding oder Android Binding.

0 Kommentare

Einen Kommentar abschicken

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