Erfahrungen aus der Cross-Plattform-Entwicklung mit Xamarin – die Übersicht

Seite 2: Zu Beginn die Projektstruktur

Inhaltsverzeichnis

Es ist wichtig, schon beim Entwurf der Architektur und des Designs plattformunabhängige Komponenten zu identifizieren und zu berücksichtigen. Eine Xamarin.Forms-App für iOS und Android besteht in der Regel aus drei Projekten in einer Visual Studio Solution (Projektmappe): MyApp.iOS und MyApp.Android für die plattformspezifischen Teile, die wiederum das geteilte Projekt MyApp.Shared referenzieren. Dabei ist es nichts weiter als eine Sammlung von Code ohne eigene Abhängigkeiten, der nur Gültigkeit in Kombination mit den Plattformprojekten in der Solution hat. Es ist auch möglich, ein .NET-Standard-2.0-Projekt für den geteilten Code zu nutzen, das eigenständig kompilierbar ist und eingebunden werden kann.

Unabhängig von der gewählten Variante besteht das Ziel darin, möglichst viel plattformunabhängigen Code in den geteilten Projekten zu haben und möglichst wenig plattformspezifischen. Es ist außerdem empfehlenswert, auf Conditional Compilation (Teile des Codes werden nur für eine bestimmte Plattform kompiliert) oder Plattformabfragen im geteilten Code zu verzichten und entsprechende Unterscheidungen beispielsweise durch den DependencyService oder andere Dependency-Injection-Frameworks in die Plattformprojekte auszulagern.

Der DependencyService ermöglicht es, innerhalb des geteilten Codes auf plattformspezifische Implementierungen zuzugreifen. Dafür werden Funktionen über ein Interface beschrieben, in jedem Plattformprojekt implementiert und mit Annotation beim DependencyService registriert. Solch eine registrierte Dependency lässt sich dann über den DependencyService anfragen und verwenden.

Wichtig ist zu beachten, dass Xamarin nicht nur für Android und iOS ist, sondern auch Windows, macOS, Tizen, tvOS und weitere Plattformen bedient. Daher gibt es immer wieder Elemente, die nach wie vor spezifisch für jede Plattform zu bauen sind. Dinge wie Push-Nachrichten und Notifications oder Sensorzugriffe funktionieren auf jeder Plattform etwas anders und werden daher von Xamarin nicht allgemein gelöst. Zusätzlich sind die Ressourcen und Assets in den Plattformprojekten zu hinterlegen sowie unter iOS der Launch-Screen als Storyboard. Diese "doppelte" Verwaltung von Ressourcen ist nötig, da iOS- und Android-Geräte unterschiedliche Anforderungen an die Größe von Ressourcen haben. Will man auch einen Launch-Screen für Android, kann man ihn als eigene Activity im Android-Projekt bauen.

Mit Xamarin.Forms erstellte UIs bestehen aus einzelnen Seiten, zwischen denen auf verschiedene Weise navigiert wird. Es gibt zwei nicht exklusive Möglichkeiten, eine Seite (ViewController) in Xamarin.Forms zu bauen: zum einen rein per Code in C#, zum anderen mit XAML für das Layout und C# für die Logik. XAML ist eine XML-basierte Layout-Sprache, die beispielsweise auch bei Windows-UWP- oder WPF-Anwendungen (Windows Presentation Foundation) zum Einsatz kommt. Beide Mittel lassen sich beliebig miteinander kombinieren. Durch die Unterstützung von Bindings kann man einfach ViewModels nutzen, um eine MVVM-Architektur (Model View ViewModel) umzusetzen. Durch Bindings wird das UI automatisch aktualisiert, wenn Properties im ViewModel geändert werden. Hängt das Binding an einem interaktiv veränderbaren Attribut, gelangt der entsprechende Wert auch automatisch in das ViewModel (bidirektionale Bindings).

Im Folgenden ist ein kleines Beispiel zu sehen, das verschiedene UI-Elemente in XAML zeigt und wie diese schließlich auf Android und iOS aussehen (Abb. 3 und 4).

Listing 1: DemoPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DemoProject.DemoPage"
             Title="DemoPage">
    <ContentPage.Content>
        <StackLayout Orientation="Vertical" Padding="16">
            <Label 
                Text="Standard Label"
                VerticalOptions="Start" HorizontalOptions="CenterAndExpand" ></Label>
            <ActivityIndicator 
                IsRunning="true" 
                WidthRequest="40" HeightRequest="40" 
                VerticalOptions="Start" HorizontalOptions="CenterAndExpand" ></ActivityIndicator>
            <DatePicker Date="{Binding Date}" ></DatePicker>
            <Switch x:Name ="MySwitch" IsToggled="{Binding IsSwitchToggled}" ></Switch>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>
Listing 2: DemoPage.xaml.cs

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace DemoProject
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class DemoPage : ContentPage
    {
        public DemoPage()
        {
            InitializeComponent();
            BindingContext = new DemoPageViewModel();
        }
    }

    public class DemoPageViewModel : INotifyPropertyChanged
    {
        private DateTime _date = DateTime.Now;
        public DateTime Date
        {
            get => _date;
            set
            {
                _date = value;
                OnPropertyChanged();
            }
        }

        private bool _isSwitchToggled;
        public bool IsSwitchToggled
        {
            get => _isSwitchToggled;
            set
            {
                _isSwitchToggled = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Es wird deutlich, dass das gleiche Xamarin.Forms-Element auf Android und iOS leicht unterschiedlich aussieht, weil das native Look & Feel durch das Mapping auf die nativen Komponenten beibehalten wird. Dieses Mapping erreichen Entwickler durch Renderer.