Prism für Xamarin.Forms

Xamarin.Forms soll dem XAML-erprobten Windows-Entwickler den Einstieg relativ leicht machen und punktet mit der, von WPF gewohnten, Kombination aus deklarativem XAML und C#.
Aber genau wie dort droht uns auch bei Xamarin.Forms die berüchtigte Code-Behind-Falle inklusive all ihrer Plagen, wie enger Kopplung von Abhängigkeiten und damit einhergehend nicht automatisiert testbarer Code. Um es erst gar nicht so weit kommen zu lassen, stellt uns Prism, wie auch in der Desktop-Version, ein ganzes Arsenal an Werkzeugen und Konzepten zur Seite.

Prism

Prism ist ein Framework zur Entwicklung von XAML-Anwendungen unter Einhaltung des MVVM-Musters. Es stehen Versionen mit unterschiedlichen Dependency Injection Containern unter der Haube zur Auswahl. Seit Version 7.0 sind die Container unter einer Abstraktionsschicht verborgen, wodurch ein späterer Austausch ohne Codeänderungen möglich wird.

Prism Project Wizard - Xamarin - 29082018-Mobile-14KB, generic.de AG

Abbildung 1: Prism Project Wizard / Bildquelle: Stefan Fritz, generic.de AG

Die einfachste Variante Prism einzusetzen ist das Template Pack aus der Visual Studio Gallery. Damit erhalten wir Projekttemplates, die mit einem komfortablen Assistenten die Erstellung unserer Solution unterstützt. Nach Auswahl der gewünschten Zielplattformen sowie des Dependency Injection Containers werden Startprojekte für jede Plattform sowie ein .NET Standard Projekt als gemeinsame Codebasis angelegt.

Das Template legt direkt eine Grundstruktur inklusiver einer ersten View und eines dazugehörigen ViewModels an und initialisiert das Ganze. Die Projektstruktur sieht bereits Ordner für Views und ViewModels vor. Neben des ersten ViewModels ist außerdem die Basisklasse ViewModelBase vorhanden, die Interfaces zur Navigation und Disposing implementiert und einiges davon als virtuelle Methoden zum Überschreiben anbietet.

Erfahrene Prism-Verwender werden das Fehlen einer Bootstrapper-Klasse bemerken. Das Bootstrapping erfolgt stattdessen in Initialisierungsmethoden. Die finden sich einerseits in der App.xaml.cs des gemeinsamen Projekts, andererseits für jede Zielplattform in Klassen, die IPlatformInitializer implementieren. Dieses Vorgehen wurde gewählt, da plattformspezifische Dienste (z.B. Zugriff auf das Dateisystem) in den jeweiligen Startprojekten initialisiert bzw. im Dependency Injection Container registriert werden müssen.

Das Framework spart uns außerdem viel Boilerplate Code beim Entwickeln. Beispielsweise implementiert die BindableBase Klasse, von der alle ViewModels ableiten das Interface INotifyPropertyChanged, kümmert sich um den Vergleich von altem und neuem Wert und löst bei Bedarf das Event OnPropertyChanged aus, wodurch die Bindings in den XAML-Views aktualisiert werden.

Prism - Xamarin - 29082018-Mobile-5KB, generic.de AG

Abbildung 2 / Bildquelle: Stefan Fritz, generic.de AG

Darüber hinaus bietet es einige Implementierungen von ICommand oder auch einen Satz nützlicher Behaviours, wie beispielsweise ein EventToCommand behaviour, wodurch in XAML Events eines Steuerelements an ein Command im ViewModel gebunden werden kann.

Dependency Injection

Das große Ziel von MVVM, Abhängigkeiten zu vermeiden und View und Code lose zu koppeln, wird von Prism durch das Dependency Injection Pattern in der Ausprägung Constructor Injection erreicht. Sprich, von einer Klasse benötigte Komponenten werden in deren Konstruktor als Parameter angefordert. Beim Laden eines Dialogs wird von Prism das dazugehörige ViewModel, sowie die davon im Konstruktor angeforderten Komponenten erzeugt.

Prism - Xamarin - 29082018-Mobile-16KB, generic.de AG

Abbildung 3 / Bildquelle: Stefan Fritz, generic.de AG

 

Durch dieses Pattern lässt sich auch der in Xamarin.Forms integrierte Service Locator DependencyService ersetzen, der plattformspezifische Funktionalitäten, die in den einzelnen Plattformprojekten implementiert sind, in den plattformunabhängigen gemeinsam genutzten Code verfügbar macht.

Identisch in Service Locator und Dependency Injection ist, dass im gemeinsam genutzten Code ein Interface definiert wird, das in den Plattformprojekten implementiert wird.

Prism - Xamarin - 29082018-Mobile-18KB, generic.de AG

Abbildung 4 / Bildquelle: Stefan Fritz, generic.de AG

Im DependencyService findet die Registrierung dann per Attribut statt.

Prism - Xamarin - 29082018-Mobile-15KB, generic.de AG

Abbildung 5 / Bildquelle: Stefan Fritz, generic.de AG

Zum Abrufen einer Instanz bietet der DependencyService die generische Methode Get an. Ihr kann als Parameter mitgegeben werden, ob eine neue Instanz erzeugt werden soll oder immer die vorgehaltene globale Instanz verwendet werden soll. Der Default hier ist, dass sich alle Konsumenten eine Instanz teilen, die einmal zur Laufzeit erstellt wird (Singleton).

Prism - Xamarin - 29082018-Mobile-12KB, generic.de AG

Abbildung 6 / Bildquelle: Stefan Fritz, generic.de AG

Der DependencyService kann aber zwei Probleme verursachen:

• Zum einen kann die konsumierende Klasse nicht ohne weiteres automatisiert getestet werden, da der statische DependencyService innerhalb aufgerufen und so nicht gemockt werden kann.

• Zum anderen bleibt es hier dem Konsumenten überlassen, wie er die registrierte Komponente abrufen will. Die Entscheidung, ob Funktionalität über einen Singleton bereitgestellt wird oder für jeden Konsumenten eine neue Instanz notwendig ist, muss ausgehend von der Implementierung dieser Funktionalität stattfinden. Deshalb muss diese Entscheidung dort getroffen werden, wo diese Funktionalität bereitgestellt wird, nicht dort, wo sie konsumiert wird.

Als Alternative zur Vermeidung dieser Probleme verwendet Prism wie bereits oben erwähnt einen Dependency Injection Container. Anstelle eines Attributs wird Interface und Implementierung beim Programmstart im Container registriert. Hier findet auch die Entscheidung, ob Singleton oder nicht, statt.

Prism - Xamarin - 29082018-Mobile-14KB, generic.de AG

Abbildung 7 / Bildquelle: Stefan Fritz, generic.de AG

Beim Navigieren zu einer neuen Seite wird deren ViewModel automatisch erzeugt. Dabei werden die im Container registrierten Abhängigkeiten als Konstruktorparameter übergeben. Damit ist auch die Testbarkeit wieder gegeben, da das ViewModel mit Mocks der Abhängigkeiten instanziiert werden kann.

Prism - Xamarin - 29082018-Mobile-9KB, generic.de AG

Abbildung 8 / Bildquelle: Stefan Fritz, generic.de AG

Ausblick

Das Framework Prism stellt mit seinen Konzepten und vielen nützlichen Erweiterungen und Services eine gelungene Bereicherung von Xamarin.Forms dar. In weiteren Artikeln werden wir uns das Navigationskonzept sowie einige der Services anschauen.

Weiterführende Links:
Github
Brianlagunas