MVVMフレームワーク「ReactiveUI」を Xamarin.iOS で使ってみる

ReactiveUI は、Reactive Extensions を全面的に取り入れた クロスプラットフォームな MVVMフレームワークです。

作者は GitHub の中の人 Paul Betts 氏、Xamarin の MVP でもあります。

元々は WPF, Silverlight, WinRT, Windows Phone に対応していましたが、Xamarin.iOS や Xamarin.Android, Xamarin.Mac にも 対応が進んで来た ので、使ってみる事にしました。

Visual Studio + WPF 等なら、nuget から取得できて楽なんでしょうけども、なにせ Mac なので、Xamarin Studio のみでいきます。

とりあえず使ってみる

Github が公開した GitHub's Xamarin starter apps, これに ReactiveUI も含まれているので、こちらを Clone して Xamarin Studio で開いてビルド、すぐ動きます。

using reactiveui 01

これ、ViewModel側で UUID を生成して、View側の Label にバインドしているのですが、何ともシンプル過ぎて…。

それでもこのフレームワークの構成を知るには十分です。

ReactiveUI に必要なもの

ソリューションツリーを見ると次の4つのプロジェクトがあります。

  • Starter-Core-Android
  • Starter-Core-iOS
  • Starter-Android
  • Starter-iOS

ViewModel-Model層

Starter-Core-xxx は、ディレクトリ的には同じ場所にあり、Android用とiOS用のプロジェクトファイル(.csproj)が用意してあるだけです。ここはアプリケーションの ViewModel-Model層になります。PCL化はされていないようですね(その内、とサイトに書いてありました)。

サンプルで用意されてる ViewModel を見てみます。

//TestViewModel.cs
using System;
using ReactiveUI;
using System.Runtime.Serialization;

namespace Starter.Core.ViewModels
{
    [DataContract]
    public class TestViewModel : ReactiveObject
    {
        string _TheGuid;
        [DataMember] public string TheGuid {
            get { return _TheGuid; }
            set { this.RaiseAndSetIfChanged(ref _TheGuid, value); }
        }

        public TestViewModel()
        {
            TheGuid = Guid.NewGuid().ToString();
        }
    }
}

MvvmCross とか、他の MVVM-FW とだいたい同じですね(そりゃそうだ)。 基底クラスの ReactiveObject が、BaseViewModel的な役割をします。(が、Reactive を冠しているだけに、随所で Rx の力が発揮される、はずです←まだ分かってない)

このコードでは、TestViewModel の生成と同時に、Guid を生成して、TheGuid プロパティに設定しています。

View層

Starter-Android, Starter-iOS はそれぞれの View層になります。

Starter-iOS の TestViewController.cs を見てみます。

TestViewController.cs

namespace Starter.Views
{
    public partial class TestViewController : ReactiveViewController, IViewFor<TestViewModel>
    {
		[省略]

        public override async void ViewDidLoad()
        {
            base.ViewDidLoad();
            this.OneWayBind(ViewModel, vm => vm.TheGuid, v => v.TheGuid.Text);

            ViewModel = await BlobCache.LocalMachine.GetOrCreateObject("TestViewModel", () => {
                return new TestViewModel();
            });
        }

        TestViewModel _ViewModel;
        public TestViewModel ViewModel {
            get { return _ViewModel; }
            set { this.RaiseAndSetIfChanged(ref _ViewModel, value); }
        }

		[省略]
    }
}

UIViewController ではなく ReactiveViewController から派生させてます。この辺もよくあるやり方。IViewFor は、今はスルーで。

バインドは this.OneWayBind で。 ViewModel の TheGuid プロパティを、View の TheGuidラベルの Text プロパティへ単方向(OneWay)バインドしてます。

TestViewModel の生成は、ここでは Akavache というストレージライブラリの生成を待ってから行っていますが、Akavache を使わない場合は普通に this.ViewModel = new TestViewModel() で OK でしょう。

これで、TestViewModelの生成 → Guidの生成 → vm.TheGuidプロパティへ設定 → vm より TheGuid の変更が通知される → View側のBindingが変更を検知 → Viewのラベルを書き換える、という流れになります。

ちょっと拡張してみる

双方向バインディング

ViewModel→View だけでなく、View→ViewModel もやってみましょう。

まず TestViewModel にプロパティを追加します。 プロパティは MyName とします。 初期値として "Enter your name" とでも設定しましょうか。

//TestViewModel.cs
namespace Starter.Core.ViewModels
{
    [DataContract]
    public class TestViewModel : ReactiveObject
    {
        string _TheGuid;
        [DataMember] public string TheGuid {
            get { return _TheGuid; }
            set { this.RaiseAndSetIfChanged(ref _TheGuid, value); }
        }

        string _myName;
        [DataMember] public string MyName {
            get { return _myName; }
            set { this.RaiseAndSetIfChanged(ref _myName, value); }
        }

        public TestViewModel()
        {
            TheGuid = Guid.NewGuid().ToString();
            
            this.MyName = "Enter your name";
        }
    }
}

次に Interface Builder で TestViewController に、UITextField と UILabel を追加し、Outlet を "MyText", "MyLabel" とします。これで Xamarin.iOS から MyText, MyLabel でインスタンスにアクセスできるはず、ですよね。

MyText, MyLabel に、vm.MyName をバインドします。

TestViewController.cs

namespace Starter.Views
{
    public partial class TestViewController : ReactiveViewController, IViewFor<TestViewModel>
    {
		[省略]

        public override async void ViewDidLoad()
        {
            base.ViewDidLoad();
            this.OneWayBind(ViewModel, vm => vm.TheGuid, v => v.TheGuid.Text);

            this.Bind(ViewModel, vm=> vm.MyName, v => v.MyText.Text);
            this.OneWayBind(ViewModel, vm => vm.MyName, v => v.MyLabel.Text);

            ViewModel = await BlobCache.LocalMachine.GetOrCreateObject("TestViewModel", () => {
                return new TestViewModel();
            });
        }

		[省略]
    }
}

編集できる MyTextthis.Bind を使って双方向バインドします。プロパティの値を表示するだけの MyLabel は、 this.OneWayBind で。

これで動かしてみます。

UITextField への入力が、vm.MyName へ適用され、その変更を MyLabel に表示させる、という流れです。

今日はこの辺で。まだ全然 Reactive じゃないですが、次回以降、Command の実装やバインディングについて試してみようと思います。

ここまでのコードは、

に置いておきます。徐々に進化させていこうと思います。