en / de
Expertisen
Methoden
Dienstleistungen
Referenzen
Jobs & Karriere
Firma
Technologie-Trends TechCast WebCast TechBlog News Events Academy

ReactiveUI und Xamarin.Forms – Ein Beispiel

ReactiveUI ist ein cross-platform Mvvm-Framework. Wie der Name vermuten lässt, setzt ReactiveUI auf den Reactive Extensions (Rx) auf. Es bietet neben WPF und UWP auch eine erstklassige Unterstützung für Xamarin Native und natürlich Xamarin Forms. Desweiteren ist es Open-Source und wurde mittlerweile in die .NET Foundation aufgenommen.

Beispiel Projekt

Anhand folgendem Beispiel möchte ich zeige, wie mit ReactiveUI und Xamarin.Forms eine Live-Suche implementiert werden kann. Die App ist einfach gestaltet. Man kann per Eingabefeld nach Ortschaften bzw. ÖV Stationen suchen. Die Suchanfragen werden jeweils an eine REST-Schnittstelle von opendata.ch abgesetzt. Anschliessend werden die Resultate in Form einer Liste dargestellt. Wer den kompletten Source Code einsehen will, findet diesen auf Github.

Anforderungen

Damit der Server jedoch nicht mit unnötigen Suchanfragen bombadiert wird, soll die Anzahl der Aufrufe begrenzt werden. Wenn der Benutzer schnell tippt, macht es wenig Sinn nach jedem Buchstaben sofort eine neue Suche abzusetzen. Die Suche soll erst starten, wenn man für eine bestimmte Zeit keine weitere Eingabe gemacht wird. Wählt man einen kurzen Grenzwert, so fallt dies dem Benutzer auch nicht negativ auf. In diesem Beispiel wurden 500ms verwendet:

live search demo

Der Aufbau

Die Solution ist in drei Projekte aufgeteilt. Jeweils ein Plattformprojekt (iOS/Android) sowie ein Projekt mit einer .Net Standard Klassenbibliothek. In dieser befindet sich die Xamarin.Forms Teil mit View und ViewModel. Es kann somit die komplette Logik zwischen den beiden Apps geteilt werden.

Das ViewModel

Das SearchViewModel beinhaltet alle relevanten Properties. Weiter findet man darin auch die Logik für die Suche. Die basis ViewModel Implementation bei ReactiveUI heisst ReactiveObject. Diese Klasse implementiert bereits INotifyPropertyChanged. Somit kann man Properties direkt wie folgt definieren:

public string SearchQuery
{
	get { return _searchQuery; }
	set { this.RaiseAndSetIfChanged(ref _searchQuery, value); }
}

public ReactiveCommand<string, List<Station>> Search
{
	get { return _searchCommand; }
	private set { this.RaiseAndSetIfChanged(ref _searchCommand, value); }
}

Im Konstruktor des ViewModels werden die einzelnen Properties initialisiert. Für den Search Command kann man die statische Hilfsmethode CreateFromTask von ReactiveUI verwenden:

  Search = ReactiveCommand.CreateFromTask<string, List<Station>>(SearchAsync, CanSearch());

Der erste Parameter ist dabei die Methode, welche einen Suchtext entgegennimmt und eine Liste von Stationen zurück liefert. Beim zweiten Parameter handelt es sich um ein IObservable<bool>. Dieses Observable definiert, ob der Command ausgeführt werden darf oder nicht. Dabei wird auf folgende Bedingungen geachtet:

Mit Rx ausgedrückt, kann dies wie folgt aussehen:

Observable.CombineLatest(
         this.WhenAnyValue(vm => vm.SearchQuery)
             .Select(searchQuery => !string.IsNullOrEmpty(searchQuery))
             .DistinctUntilChanged(),
         this.WhenAnyObservable(x => x.Search.IsExecuting)
             .DistinctUntilChanged(),
         (hasSearchQuery, isExecuting) => hasSearchQuery && !isExecuting)
     .Do(cps => System.Diagnostics.Debug.WriteLine($"Can Perform Search: {cps}"))
     .DistinctUntilChanged()

CombineLatest führt dabei zwei Observables so zusammen, dass jeweils immer nur der letzte Wert berücksichtigt wird. Der dritte Parameter ist eine Funktion, welche die beiden letzten Werte zu einem einzigen Boolean zusammenführt.

Live Suche (Throttling)

Die Live Suche kann man ebenfalls relativ einfach mit Rx und den Hilfsmethoden von ReactiveUI implementieren. Mit Hilfe der Throttle Funktion können schnell aufeinanderfolgende Eingaben ausgefiltert werden. Dies lässt sich gut mit einem sogenannten Marble Diagramm erklären.

Marble Diagramm für Throttle/Debounce

Die obere Zeitachse stellt den Input Stream dar und die untere Zeitachse den Output Stream. Die Implementation im ViewModel sieht dann wie folg aus:

// erstellt ein IObservable<string> von SearchQuery
this.WhenAnyValue(x => x.SearchQuery)
    // drosselt Änderungen von SearchQuery, sodas diese erst weitergereicht warden,
    // wenn für 500ms keine weiteren Änderungen passieren
    .Throttle(TimeSpan.FromMilliseconds(500), TaskPoolScheduler.Default)
    // Callback soll auf dem UI Thread stattfinden
    .ObserveOn(RxApp.MainThreadScheduler)
    // SearchCommand soll ausgeführt warden (sofern CanExecute == true)
    .InvokeCommand(Search)

An diesem Beispiel sieht man gut, wie ein komplexer Ablauf mit Hilfe von Rx auf einfache Weise umsätzen lässt. Würde man die gleiche Funktionalität in einem imperativen Still implementieren wollen, müsste man zusätzliche Zustands-Variablen und einen Timer verwenden. Rx erlaut jedoch eine deklartive Schreibweise. Diese führt meiner Meinung nach zu verständlicherem Code.

Die View

Nachdem wir nun das ViewModel implementiert haben, muss noch die View umgesetzt werden. Diese kann man entweder mit XAML oder per Code aufbauen. Bindings werden bei ReactiveUI jedoch bewusst im Code-Behind als Expressions geschrieben:

private void InitializeBindings()
{
    // Search Query
    this.Bind(ViewModel, x => x.SearchQuery, c => c.SearchEntry.Text)
        .DisposeWith(_bindingsDisposable);

    // Search Command
    this.BindCommand(ViewModel, x => x.Search, c => c.SearchButton, vm => vm.SearchQuery)
        .DisposeWith(_bindingsDisposable);

    // Activity Indicator
    this.WhenAnyObservable(x => x.ViewModel.Search.IsExecuting)
        .BindTo(ActivityIndicator, c => c.IsRunning)
        .DisposeWith(_bindingsDisposable);

    // Results
    this.OneWayBind(ViewModel, x => x.SearchResults, c => c.SearchResults.ItemsSource)
        .DisposeWith(_bindingsDisposable);
}

Das Command-Binding führt dabei nicht nur den Command bei einem Klick aus sonder kümmer sich auch darum den Button zu aktiviren und deaktivieren.

Fazit

Bisher hatte ich zwar noch nicht die Gelegenheit ReactiveUI in grösseren Projekten einzusetzen. Tortzdem macht das Framework für mich auf den ersten Blick einen guten Eindruck. Es bringt alle Werkzeuge mit, welche ich von einem Mvvm-Framework erwarte. Darüberhinaus liefert es viele nütziche Extensions. Diese erlauben es Bindings und Commands mit Rx zu verknüpfen.

Wer allerdings noch nie mit Rx gearbeitet hat, wird zu beginn einen etwas schweren Einstieg habe, da ReactiveUI sehr stark darauf aufbaut. In diesem Fall sollte man nebst der Dokumentation auch unbedingt die Beispiele genau anschauen.

Kommentare

Schreiben Sie einen Kommentar

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

Newsletter - aktuelle Angebote, exklusive Tipps und spannende Neuigkeiten

 Jetzt anmelden
NACH OBEN
Zur Webcast Übersicht