Blog

MVVM mit Xamarin Classic: Die Grundlagen

Pascal Naber

Aktualisiert Oktober 22, 2025
4 Minuten

Mit Xamarin ist es möglich, Apps für mehrere Plattformen (Windows Phone, iOS, Android) zu entwickeln. Der Nachteil dabei ist, dass Sie für jede Plattform eine Ansicht mit Codebehind entwickeln müssen. Dies führt zu doppeltem Code in der gesamten Lösung. Dies könnte teilweise durch die Verwendung einer Shared Library vermieden werden. Diese Bibliothek kann z.B. Klassen enthalten, die Funktionen zur Nutzung von Webservices bieten. Der Teil, der sich nicht vermeiden lässt, ist der UI-spezifische Code. Ein weiterer Nachteil dieses Ansatzes ist die mangelnde Testbarkeit, da Sie Unittests pro Plattform erstellen und auch den UI-Code dahinter testen müssen. Die Klassen im Code dahinter haben zu viele Verantwortlichkeiten, das ist nicht SOLID.

Die Standardarchitektur sieht wie folgt aus:

xamarindefaultarchitecture

An dieser Stelle bietet MVVM eine Lösung.

Bei der Anwendung von MVVM wird für jeden einzelnen Bildschirm in der Anwendung ein ViewModel erstellt. Die drei Ansichten (eine für jede Plattform) werden an das entsprechende ViewModel gebunden. Dadurch wird der Codebehind überflüssig und kann gelöscht werden. Die ViewModels befinden sich in der Shared Library. Mit MVVM sieht die Architektur wie folgt aus:

xamarinmvvmarchitecture

Die Testbarkeit und Wartbarkeit werden auf verschiedene Weise verbessert:

  • Es muss nur noch eine Klasse, das ViewModel, erstellt und getestet werden, anstatt drei Klassen mit Code dahinter.
  • Das ViewModel hat keinen plattformspezifischen UI-Code.
  • Die Trennung der Logik von der Ansicht bietet die Möglichkeit, Abhängigkeiten in das ViewModel zu injizieren. Die ViewModels können mit Mocks und/oder Stubs getestet werden.

Es gibt noch einige Fragen zu klären:

  • Die Ansicht und das ViewModel sind lose gekoppelt. Die Ansicht muss auf das entsprechende ViewModel verweisen.
  • Sie müssen ein Dependency Injection Framework auswählen.

Es ist nicht nötig, das Rad neu zu erfinden. Es gibt mehrere MVVM-Frameworks, die dies und noch viel mehr bieten. Neben MvvmLight können Sie auch MvvmCross verwenden.

Die nächsten Codesnippets zeigen Beispiele dafür, wie der Code vor und nach der Anwendung von MVVM mit MvvmCross aussieht.

Hier sehen Sie die klassische Nicht-MVVM-Variante einer Ansicht.

<?xml Version="1.0" Verschlüsselung="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:orientation="vertikal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Schaltfläche
        android:text="Vorherige"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/buttonPrevious"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true" />
    <Schaltfläche
        android:text="Nächste"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/buttonNext"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true" />
    <TextView
        android:text="Medium Text"
        android:textAppearance="?android:attr/textAussehenMedium"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/textBeschreibung"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp" />
</RelativeLayout>

Der 'Code dahinter' für die Ansicht. Beachten Sie die Abhängigkeit vom TodoTaskService. Die gleiche Art von Code sollte auch für andere Plattformen programmiert werden.

[Aktivität(Etikett = "Naber.Tasks.UI.Droid", MainLauncher = true, Ikone = "@drawable/icon")]
öffentlich Klasse MainActivity : Aktivität
{
    Schaltfläche SchaltflächeNächste;
    Schaltfläche SchaltflächeVorherige;
    TextView textBeschreibung;
    TodoTaskService Service = neu TodoTaskService();
    IDataManager<TodoTask> Daten = null;
    geschützt asynchron Überschreiben Sie void OnCreate(Bündel Bündel)
    {
        Basis.OnCreate(bundle);
                    
        SetContentView(Ressource.Layout.Main);
        var Aufgaben = warten Sie service.GetTodoTasksAsync();
        diese.data = neu DataManager<TodoTask>(Aufgaben);
                  
        SchaltflächeNächste = FindViewById<Schaltfläche>(Ressource.Id.buttonNext);
        SchaltflächeVorherige = FindViewById<Schaltfläche>(Ressource.Id.buttonPrevious);
        textBeschreibung = FindViewById<TextView>(Ressource.Id.textDescription);
        buttonNext.Click += buttonNächster_Klick;
        buttonPrevious.Click += buttonPrevious_Click;
        UpdateUi();
    }       
    void buttonPrevious_Click(Objekt Absender, EventArgs e)
    {
        data.MovePrevious();
        UpdateUi();                        
    }
    void buttonNächster_Klick(Objekt Absender, EventArgs e)
    {
        data.MoveNext();
        UpdateUi();            
    }
    privat void UpdateUi()
    {
        textBeschreibung.Text = data.Current.Description;                      
        buttonPrevious.Enabled = data.CanMovePrevious;
        buttonNext.Enabled = data.CanMoveNext;
    }
}

Bei Verwendung von MvvmCross kann der Code Behind entfernt werden. Die Ansicht unterstützt die Datenbindung. Die fettgedruckten Zeilen werden hinzugefügt und sorgen für die Bindung der Eigenschaft des Steuerelements (erster String) an die Eigenschaft oder den Befehl im ViewModel (zweiter String).

<?xml Version="1.0" Verschlüsselung="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:local="https://schemas.android.com/apk/res-auto"
    android:orientation="vertikal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Schaltfläche
        android:text="Vorherige"
        local:MvxBind="Click PreviousCommand"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/buttonPrevious"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true" />
    <Schaltfläche
        android:text="Nächste"
        local:MvxBind="Klicken Sie auf NextCommand"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/buttonNext"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true" />
    <TextView
        local:MvxBind="Textbeschreibung"
        android:textAppearance="?android:attr/textAussehenMedium"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/textBeschreibung"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp" />
</RelativeLayout>

Das ViewModel wird in einer gemeinsam genutzten Bibliothek erstellt, so dass alle Zielplattformen es verwenden können. Beachten Sie, dass es keine Abhängigkeit von einer Instanz des TodoTaskService gibt. Er wird injiziert. Dies kann in der Initialisierung von MvvmCross konfiguriert werden, die ebenfalls wiederverwendeter Code ist.

öffentlich Klasse MainViewModel : MvxViewModel
{
    ITodoTaskService taskService;
    IDataManager<TodoTask> Aufgaben;
    öffentlich MainViewModel(ITodoTaskService taskService)
    {
        diese.taskService = taskService;
    }
    öffentlich asynchron Überschreiben Sie void Start()
    {
        diese.aufgaben = neu DataManager<TodoTask>(warten Sie diese.taskService.GetTodoTasksAsync());
        diese.tasks.MoveFirst();
        Rebind();
        Basis.Start();
    }
    privat void Neu binden()
    {
        dieseBeschreibung = diese.tasks.Current.Description;
       
        NextCommand.RaiseCanExecuteChanged();
        PreviousCommand.RaiseCanExecuteChanged();
    }
    privat String Beschreibung;
    öffentlich String Beschreibung
    {
        erhalten. { return diese.description; }
        einstellen.
        {
            diese.Beschreibung = Wert;
            RaisePropertyChanged(() => Beschreibung);
        }
    }   
    
    privat MvxCommand nextCommand;
    öffentlich MvxCommand NächsterBefehl
    {
        erhalten.
        {
            diese.nextCommand = diese.nextCommand ?? neu MvxCommand(NavigateToNext, CanNavigateNext);
            return diese.nextCommand;
        }
    }
    privat bool CanNavigateNext()
    {
        return diese.tasks.CanMoveNext;
    }
    öffentlich void NavigierenZumNächsten()
    {
        diese.tasks.MoveNext();
        Rebind();
    }
    privat MvxCommand previousCommand;
    öffentlich MvxCommand VorherigerBefehl
    {
        erhalten.
        {
            diese.previousCommand = diese.previousCommand ?? neu MvxCommand(NavigateToPrevious, CanNavigatePrevious);
            return diese.previousCommand;
        }
    }
    privat bool CanNavigatePrevious()
    {
        return diese.tasks.CanMovePrevious;
    }
    öffentlich void ZuVorherigemNavigieren()
    {
        diese.tasks.MovePrevious();
        Rebind();
    }
}

Dieses einfache Beispiel zeigt, wie Sie MVVM auf Xamarin Classic anwenden können. Ein solider Weg zur Entwicklung von Geschäftsanwendungen.

Das vollständige Muster können Sie hier herunterladen.


Verfasst von

Pascal Naber

Contact

Let’s discuss how we can support your journey.