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 で開いてビルド、すぐ動きます。
これ、ViewModel側で UUID を生成して、View側の Label にバインドしているのですが、何ともシンプル過ぎて…。
それでもこのフレームワークの構成を知るには十分です。
ソリューションツリーを見ると次の4つのプロジェクトがあります。
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
プロパティに設定しています。
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();
});
}
[省略]
}
}
編集できる MyText
は this.Bind
を使って双方向バインドします。プロパティの値を表示するだけの MyLabel
は、 this.OneWayBind
で。
これで動かしてみます。
UITextField への入力が、vm.MyName へ適用され、その変更を MyLabel に表示させる、という流れです。
今日はこの辺で。まだ全然 Reactive じゃないですが、次回以降、Command の実装やバインディングについて試してみようと思います。
ここまでのコードは、
に置いておきます。徐々に進化させていこうと思います。