C#³

Xamarin 3: Plattformübergreifende App-Entwicklung
Kommentare

Und plötzlich programmieren wir iPhone-Apps. Und Android-Apps. Obwohl wir doch eigentlich ein reiner Microsoft-Schuppen sind und C# die ewige Treue geschworen haben. Danke, Xamarin.

Hand aufs Herz: Als Microsoft-Entwickler ist man Kummer mit mobilen Plattformen gewöhnt. Wenn Kunden nach Apps fragen, meinen sie iPhone und Android. Selbst als begeisterter Windows-Phone-Nutzer kann man die Augen nicht vor Verkaufsstatistiken verschließen und muss zwangsweise feststellen: Will man einen Großteil seiner Zielgruppe erreichen, hat die Microsoft-Plattform im mobilen Sektor noch immer nur Priorität Nummer drei. Bevor Xamarin die Bühne betrat, hieß das: drei Programmiersprachen beherrschen, drei User Experience Guidelines studieren und dann drei Mal die gleiche Funktionalität entwickeln. Oder man begnügte sich mit einer HTML-App, die zwar überall funktionierte, aber dann doch nirgends begeisterte.

„We knew there had to be a better way to create apps“

Plattformübergreifende Entwicklung unter .NET gab es auch schon früher, am bekanntesten durch das Open-Source-Projekt Mono. Nach der Entscheidung von Novell, dessen Weiterentwicklung im Mai 2011 einzustellen, machten die verbliebenen Mono-Entwickler aus der Not eine Tugend: Xamarin war geboren. In der „Geburtsanzeige“ auf dem Blog des Gründers Miguel de Icaza wurde die inhaltliche Marschrichtung vorgegeben: „Build a new commercial .NET offering for iOS and Android“. Was darauf folgte, hat die App-Entwicklung mit .NET revolutioniert: Einfach gesprochen übersetzt Xamarin C#-Code in native Apps und ermöglicht die Wiederverwendung von Code über Plattformgrenzen hinweg. Damit wurde C#-Entwicklern die ganze Welt der iOS- und Android-Devices eröffnet und die App-Entwicklung konnte deutlich beschleunigt werden. Zumindest in der Theorie: Wer sich an die Entwicklung einer App für alle Plattformen wagte, der wurde schnell von der Realität eingeholt. Der Anteil von wiederverwendbarem Code war nämlich meist gar nicht so groß wie anfangs gedacht.

Durchschnittliche Apps strotzen üblicherweise nicht vor komplizierter Logik; in den meisten Fällen wird mit Services kommuniziert, die dann die eigentliche Arbeit verrichten. Viel aufwändiger ist die Benutzeroberfläche – und da war es mit der Wiederverwendbarkeit schnell vorbei. Schließlich muss eine native iPhone-App wie eine iPhone-App aussehen und eine Android-App wie eine Android-App. Und schließlich gibt es unterschiedliche Controls, Navigationsparadigmen und Designs. An dieser Stelle wurden auch die jeweiligen Betriebssysteminterna schnell für den Entwickler deutlich: Man musste sich unter Android mit Activities beschäftigen und unter iOS mit dem dort gebräuchlichen MVC Pattern. Der vielleicht größte Vorteil blieb somit bisher in der Verwendung einer einheitlichen Programmiersprache. Es musste zwar nicht weniger Code geschrieben werden, aber der immerhin in C#.

Xamarin.Forms

Mit Xamarin 3, fast auf den Tag drei Jahre nach der Gründung erschienen, wurde eine Lösung für dieses Problems präsentiert: Xamarin.Forms. Unter diesem Namen verbirgt sich eine zusätzliche Abstraktionsschicht über Benutzeroberflächen: Das UI wird zunächst plattformunabhängig definiert und anschließend je nach Plattform in das dort übliche native Pendant übersetzt. Durch die Abstraktion steht natürlich nur der kleinste gemeinsame Nenner aller Plattformen zur Verfügung. Steuerelemente, die es nur unter Android gibt, können so beispielsweise nicht platziert werden (dazu aber später mehr).

Im Moment stehen neunzehn Steuerelemente zur Verfügung, etwas verwirrend spricht Xamarin dabei von Views. Neben Button, einer ein- und mehrzeiligen TextBox (Entry bzw. Editor), Label und Image stehen auch komplexere Controls wie DatePicker, ListView, TableView und SearchBar bereit. Die Deklaration erfolgt entweder in XAML oder im Code-Behind, wobei die Namen der Steuerelemente und Properties von denen der .NET-Plattformen abweichen.

Zur Anordnung der Steuerelemente stehen zusätzlich einige Container-Steuerelemente (Layouts) zur Verfügung: u. a. ein StackLayout (ähnlich dem bekannten StackPanel), ein AbsoluteLayout zur absoluten sowie ein RelativeLayout zur relativen Positionierung, ein GridLayout und eine ScrollView.

Die oberste Hierarchie in der Gestaltung von Xamarin.Forms-Benutzeroberflächen bilden Pages ab: Neben einer simplen ContentPage sorgen eine eigene MasterDetailPage, eine NavigationPage, eine TabbedPage und eine CarouselPage für die Abbildung der häufigsten Seitentypen.

Hello, Forms

Beginnen wir mit einem einfachen Beispiel. Nach der Installation von Xamarin stehen in Visual Studio mehrere Templates zur Auswahl, darunter zwei „Blank-App“-Vorlagen für die Verwendung von Xamarin.Forms. Ihr Unterschied liegt in der Art und Weise, wie Codesharing funktioniert.

Abb. 1: Xamarin-Projektvorlagen in Visual Studio 2013

Xamarin.Forms Portable verwendet eine Portable Class Library (PCL), bei der die gemeinsame Funktionalität aller gewünschten Zielplattformen zur Auswahl steht. Um plattformspezifischen Code einfügen zu können, wird in der PCL ein Interface bereitgestellt und je Plattform in einem separaten Projekt implementiert.

Xamarin.Forms Shared erzeugt neben den plattformspezifischen Projekten ein Shared Project (Abb. 2). Im Gegensatz zur PCL führt dieses Projekt zu keiner eigenen DLL, stattdessen wird der enthaltene Code automatisch zu den plattformspezifischen Projekten hinzugefügt und mitkompiliert. Durch Compilerdirektiven (#if WINDOWS_PHONE) kann auf einzelne Plattformen speziell eingegangen werden.

Abb. 2: „Xamarin.Forms-Shared“-Projekt

Wenn Sie ein reines Xamarin-Projekt erstellen, ist die Wahl der Vorlage eher Geschmacksache. Wenn sie ihren geteilten Code aber auch für weitere .NET-Profile (z. B. Windows-Store-Apps) zur Verfügung stellen wollen, sollten Sie sich für die Portable-Variante entscheiden. Detailliertere Informationen zu den beiden Optionen finden Sie auch hier, in unserem Beispiel verwenden wir Xamarin.Forms Shared.

Das Template erstellt in diesem plattformunabhängigen Projekt eine App-Klasse, in der eine statische GetMainPage-Methode bereits exemplarisch implementiert ist. In Listing 1 sehen Sie, wie durch Verwendung einer ContentPage, eines StackLayouts und zweier Controls eine einfache Benutzeroberfläche im Code erstellt werden kann. Mehr ist vorerst nicht zu tun, in den plattformspezifischen Projekten wird diese Methode aufgerufen und in native Controls konvertiert. Bei der Verwendung des DatePickers wird dies besonders deutlich: In Abbildung 3 sehen Sie, wie dieses Steuerelement auf den unterschiedlichen Plattformen aussieht.

public class App
{
  public static Page GetMainPage()
  {
    var stackLayout = new StackLayout();

    stackLayout.Children.Add(new Label() 
      { 
        Text = "Date of Appointment:"
    });

    stackLayout.Children.Add(new DatePicker());

    return new NavigationPage(
      new ContentPage()
      {
        Content = stackLayout,
        Title = "DatePicker"
    });
  }
}

Abb. 3: Plattformspezifische Darstellung des „DatePickers“ unter Android, Windows Phone und iOS

Datenbindung

Gehen wir einen Schritt weiter und begutachten ein etwas realistischeres Szenario: eine App, die Daten von einem Service konsumiert und mittels Data Binding darstellt. Als Beispiel erstellen wir eine Kochrezepte-App – falls Sie mitkochen möchten, ist der verwendete Service hier verfügbar.

Ziel ist auch nun wieder, möglichst viel Sourcecode über die Plattformgrenzen hinweg zu teilen, daher wird im Shared Project die meiste Arbeit anfallen. Wir erstellen ein Model (Listing 2) für unsere Rezepte und eine Klasse RecipeService, die sich um das Laden der Daten kümmert (Listing 3). Dieser Sourcecode könnte genau so in einer Windows-Store-App stehen, auch in Xamarin können Sie von async/await Gebrauch machen, auch in Xamarin können Sie NuGet-Packages wie Json.NET einbinden (allerdings nur in den plattformspezifischen Projekten und unter gewissen Voraussetzungen).

public class Recipe
{
  public int Id { get; set; }
  public string Title { get; set; }
  public string ImagePath { get; set; }
  public int Rating { get; set; }
}
public class RecipeService
{
  private static string RECIPE_BASEURL = "http://mycookbookservice.azurewebsites.net/api/Recipes";

  public async static Task<IList> GetRecipes()
  {
    var url = new Uri(RECIPE_BASEURL);
    var http = new HttpClient();
    var json = await http.GetStringAsync(url);
    var recipes = JsonConvert.DeserializeObject<List>(json);
    return recipes;
  }
}

Legen wir auch diesmal den Fokus auf das Erstellen der Benutzeroberfläche, verwenden wir diesmal aber das MVVM Pattern und die XAML-Notation. Das CookbookViewModel (Listing 4) soll zur Darstellung der verfügbaren Rezepte dienen und beinhaltet eine ObservableCollection sowie eine asynchrone LoadRecipes-Methode. Über Plattformgrenzen hinweg funktionieren INotifyCollectionChanged und IPropertyChanged: Wenn Sie ein Element zur Auflistung hinzufügen, aktualisiert sich das UI, Gleiches gilt für Property-Änderungen. Wer bereits vor Xamarin 3 Benutzeroberflächen für Android oder iOS gebaut hat, weiß diese Vereinfachung zu schätzen: Wieder kann bestehendes .NET-Know-how plattformübergreifend genutzt werden.

public class CookbookViewModel
{
  public CookbookViewModel ()
  {
    this.Recipes = new ObservableCollection();
  }

  public ObservableCollection Recipes { get; set; }
 public async Task LoadRecipes()
  {
    var recipes = await RecipeService.GetRecipes();
    foreach (var item in recipes)
    {
      this.Recipes.Add(item);
    }
  }
}

Bleibt noch die View, die diesmal unter Verwendung von XAML erstellt werden soll (Listing 5). Als Item-Template steht eine Forms XAML Page zur Verfügung, die eine *.xaml-Datei inklusive Code-Behind erstellt. Die standardmäßig vorgeschlagene ContentPage ändern wir auf TabbedPage; hier wird die Arbeitsweise von Xamarin 3 noch deutlicher. Als Kindelemente fügen wir mehrere ContentPages hinzu, später könnten so beispielsweise die am besten bewerteten Rezepte auf der ersten Seite, danach alle Vorspeisen, Hauptgerichte etc. gruppiert dargestellt werden. Als Steuerelement verwenden wir die ListView und binden die ItemsSource an die Recipes Property aus dem ViewModel. Wie aus XAML bekannt, bestimmen wir mithilfe eines DataTemplates, wie die einzelnen Zeilen der Liste aussehen sollen. Aber Achtung: Ein DataTemplate beinhaltet nicht direkt die gewünschten Steuerelemente, sondern eine Cell. Die allgemeinste Form, in der Sie selbst durch StackLayout oder dergleichen Elemente positionieren können, nennt sich ViewCell. Für unser Beispiel ist eine ImageCell aber besser geeignet: Wir können das Bild und den Titel des Rezepts an die beiden Properties ImageSource und Text binden.



  
    <!--?xml version="1.0" encoding="utf-8" ?-->
<tabbedpage xmlns="h ttp://xamarin.com/schemas/2014/forms" xmlns:x="h ttp://schemas.microsoft.com/winfx/2009/xaml" x:class="CookbookApp.MainXamlPage" title="CookbookApp">
  <tabbedpage.children>
    <contentpage title="Top 10">
      <!-- TODO -->
    </contentpage>
    <contentpage title="All">
      <listview itemssource="{Binding Recipes}">
        <listview.itemtemplate>
          <datatemplate>
            <imagecell imagesource="{Binding ImagePath}" text="{Binding Title}">
          </imagecell></datatemplate>
        </listview.itemtemplate>
      </listview>
    </contentpage>
</tabbedpage.children>
</tabbedpage>
       

Das Ergebnis kann sich sehen lassen: Die App zeigt unter Windows Phone, Android und iOS die jeweils gewohnte Darstellung.

Abb. 4: Cookbook-App auf allen Plattformen (Android, Windows Phone, iOS)

Zu kleiner gemeinsamer Nenner?

Was aber tun, wenn der kleinste gemeinsame Nenner von Xamarin.Forms nicht mehr ausreicht? Was, wenn man ein spezielles Android-Feature verwenden möchte? Was, wenn es das neue Windows-Phone-Control noch nicht auf allen Plattformen gibt oder zumindest einzelne Properties nicht in Xamarin.Forms abgebildet sind?

Xamarin hat auch hier einige Antworten parat. Zum einen zwingt Sie niemand dazu, für ein ganzes Projekt Xamarin.Forms einzusetzen. Sie können beispielsweise in Ihrem Windows-Phone-Projekt eigene, komplett von Xamarin losgelöste Pages erstellen und nur für einzelne Seiten die Vorteile von Xamarin.Forms nutzen.

Auch eine Ebene tiefer gibt es Eingriffsmöglichkeiten: Sie können eigene Xamarin.Forms-Steuerelemente erstellen, müssen dann aber auch Renderer für jede Plattform entwickeln, die den entsprechenden plattformspezifischen Code erzeugen.

Das Schöne an Xamarin.Forms ist, dass die Verwendung sehr transparent gelungen ist und trotz der Codewiederverwendung bei Bedarf alle plattformspezifischen Freiheiten zur Verfügung stehen.

Zu schön, um wahr zu sein

Ja, Xamarin.Forms ist tatsächlich ein großer Sprung vorwärts. Damit wird eine Lücke geschlossen, die bisher noch immer für den größten Aufwand bei der Entwicklung plattformunabhängiger Apps verantwortlich war: die Erstellung der Benutzeroberfläche. Es soll jedoch nicht unerwähnt bleiben, dass diese schöne Welt nicht kostenlos zur Verfügung steht: Um professionell arbeiten zu können, sind pro Entwickler für Android und iOS jeweils 999 US-Dollar fällig, werden beide gemeinsam gekauft, gibt es immerhin 10 Prozent Rabatt.

Auch die Visual-Studio-Integration könnte noch besser sein, beispielsweise gibt es für die Erstellung von XAML-Dateien keine IntelliSense-Unterstützung. Dass für die Entwicklung von iOS-Apps zusätzlich Mac-Hardware angeschafft werden muss, um die App-Packages legal erzeugen und testen zu können, darf man nicht den Damen und Herren aus San Francisco anlasten – da ist die Quelle des Übels wenige Kilometer südlich zu suchen.

Alles in allem macht die Arbeit mit Xamarin.Forms aber richtig Spaß – und gerade wenn das Budget und die Zeit knapp sind, können damit in kurzer Zeit alle Plattformen bedient werden. Und zwar mit C#. Danke, Xamarin.

Aufmacherbild: background of lots of rails crossing seen from above von Shutterstock / Urheberrecht: Jule_Berlin

Roman Schacherl

Autor

Roman Schacherl

  Roman Schacherl ist Gründer der Firma softaware gmbh (www.softaware.at) und entwickelt individuelle Lösungen auf Basis von Microsoft-Technologien. Er ist Autor mehrerer Fachartikel, nebenberuflich Lehrender an der FH Hagenberg (Österreich) und als Sprecher auf Entwicklerkonferenzen tätig.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -