Xamarin.Forms と ReactiveProperty で快適MVVM生活

 Xamarin.Forms は、Xamarin に新たに搭載されたクロスプラットフォームUIフレームワーク&MVVMフレームワークです。

 ReactiveProperty は、MVVMの(特に ViewModelの)実装を強力にサポートしてくれる、Reactive Extensions を基盤としたライブラリです。

両者を組み合わせると、Android/iOSアプリが COOL な感じで書けるんじゃないか、という事で試してみました。

0. 環境など

Mac + Xamarin Studio を使いますが、Windows + Visual Studio + Xamarin-Addin でもイケると思います。

1. 導入

プロジェクトの作成

新規ソリューションを、[C#]−[Mobile Apps]−[Blank App(Xamarin.Forms Portable)]で作成します。

PCL の Profile を変更

 作成されたソリューションの一番上にあるプロジェクト(.Android とか .iOS が付いていないやつ)のプロジェクト設定を開いて Profile を PCL 4.5 - Profile49 に変更します。元々の Profile78 では ReactiveProperty が Nuget からインストールできないためです。最近のプラットフォームを対象にするなら、あまり影響はなさそうです。

using xamarin forms with reactiveproperty 01

Nuget で Reactive Extensions と ReactiveProperty を追加

 メニューの[プロジェクト]ー[Add Packages]で Nuget のダイアログを開き、図のように 「Reactive Extensions - Main Library」と「ReactiveProperty Portable」を追加します。

using xamarin forms with reactiveproperty 02

(Reactive Extensions の追加の際、なにやらWarningが出るようですが、とりあえず進めます。)

2. ViewModel の実装

 PCL のプロジェクトに、FirstViewModel.cs を作成します。  FirstViewModel は、以下のようなプロパティとコマンドを持ちます。

  • InputTextプロパティ : EditBox の入力に応じて更新
  • DisplayTextプロパティ : InputText の変化から1秒後に、InputText を大文字にして更新
  • Clearコマンド : InputText が 'clear' の時のみ有効。実行すると InputText を空にする。

これらの実装が下のようになります。

FirstViewModel.cs

using System;
using Codeplex.Reactive;
using System.Reactive.Linq;

namespace FormsWithRxProperty.ViewModels
{
    public class FirstViewModel
    {
        private readonly ReactiveProperty<string> _inputText = 
            new ReactiveProperty<string>("Hoge");
        public ReactiveProperty<string> InputText 
        { 
            get { return _inputText; }
        }

        public ReactiveProperty<string> DisplayText
        {
            get; private set;
        }

        public ReactiveCommand Clear
        {
            get; private set;
        }

        public FirstViewModel()
        {
            // DisplayText は、InputText の変更から1秒後に大文字にして更新
            this.DisplayText = _inputText
                .Delay(TimeSpan.FromSeconds(1))
                .Select(x => x.ToUpper())
                .ToReactiveProperty();

            // InputText が `clear` の時に実装可能
            this.Clear = _inputText
                .Select(x => x.Equals("clear"))
                .ToReactiveCommand();
            // 実行されたら、InputText を空にする
            this.Clear.Subscribe(_ => _inputText.Value = String.Empty);
        }
        
    }
}

 面倒な INotifyPropertyChanged の実装が必要なく、すっきりと記述できます。  また、他のプロパティに関連して(反応して)値が変化するプロパティや、コマンドの利用可否などが、Reactive Extensions の機能により、流れるように記述できます。

3. 画面及び ViewModel との Binding の実装

 画面(UI)は、Xamarin.Forms の恩恵で、Android/iOS 共通で実装できます。XAML も使えますが、よく知らないのでコードでUIを記述します。

 PCL のプロジェクトに、 FirstPage.cs を作成し、以下のように実装します。

FirstPage.cs

using System;
using Xamarin.Forms;
using FormsWithRxProperty.ViewModels;

namespace FormsWithRxProperty.Pages
{
    public class FirstPage : ContentPage
    {
        public FirstPage()
        {
            // UI
            var entry = new Entry
            {
                Text = "Hello, Forms!",
                VerticalOptions = LayoutOptions.Center,
                HorizontalOptions = LayoutOptions.FillAndExpand,
            };

            var label = new Label
            {
                VerticalOptions = LayoutOptions.Center,
                HorizontalOptions = LayoutOptions.CenterAndExpand,
            };

            var button = new Button
            {
                Text = "Clear (type 'clear' to enable)",
                VerticalOptions = LayoutOptions.Center,
                HorizontalOptions = LayoutOptions.FillAndExpand,
            };

            this.Content = new StackLayout
            {
                Padding = new Thickness(50f),
                VerticalOptions = LayoutOptions.Start,
                HorizontalOptions = LayoutOptions.Fill,
                Orientation = StackOrientation.Vertical,
                Children =
                {
                    entry,
                    label,
                    button
                }
            };

            // ViewModel との Binding
            this.BindingContext = new FirstViewModel();
            entry.SetBinding<FirstViewModel>(Entry.TextProperty, vm=>vm.InputText.Value);
            label.SetBinding<FirstViewModel>(Label.TextProperty, vm=>vm.DisplayText.Value);
            button.SetBinding<FirstViewModel>(Button.CommandProperty, vm=>vm.Clear);
        }
    }
}

 ちょっと長いですが、画面に「エディットボックス」「ラベル」「ボタン」が縦に並んでいるだけです。

 下部の4行で、FirstViewModel の各プロパティ、コマンドと Bind しています。

 もともとあった App.cs は、FirstPage を生成するだけにします。

App.cs

using System;
using Xamarin.Forms;
using FormsWithRxProperty.Pages;

namespace FormsWithRxProperty
{
    public class App
    {
        public static Page GetMainPage()
        {	
            return new FirstPage();
        }
    }
}

動かす!

.Android か .iOS の付いたプロジェクトをスタートアップにして、実行します。

追記 2014.9.10

実機で動作確認するの忘れてました(実機はAOTなのに対してiOSシミュレータはJITなのでリフレクションとかが普通に動いてしまう)。 実機でも問題なく動作しました!

追記 2014.9.11 INotifyPropertyChanged の利用

ViewModel は INotifyPropertyChanged を実装して作成するのが一般的です。既にそのようにして作られた ViewModel でも IObservable 化して、ReactiveProperty で利用できます。

SecondViewModel.cs

public class SecondViewModel : INotifyPropertyChanged
{
    public ReactiveProperty<string> ValidationAttr { get; private set; }
    public event PropertyChangedEventHandler PropertyChanged;

    private string _myName = "HoGe";
    public string MyName 
    {
        get { return _myName; }
        set 
        { 
            if (_myName == value) return;

            _myName = value;
            PropertyChanged(this, new PropertyChangedEventArgs("MyName"));
        }
    }

    public ReactiveProperty<string> LowerText { get; private set; }

    private ICommand _resetCommand;
    public ICommand ResetCommand
    {
        get
        {
            return _resetCommand ?? (_resetCommand = 
                new Xamarin.Forms.Command(() => MyName = "XAAAAMAAARIN!!"));
        }
    }

    public SecondViewModel()
    {
        this.LowerText = this.ObserveProperty(x => x.MyName)
            .Select(x => x.ToLower())
            .ToReactiveProperty();
    }
}

using xamarin forms with reactiveproperty 04

まとめ

 Reactive Extensions のメリットを活かして MVVM を構築できる ReactiveProperty と、ワンソースで Android/iOS の画面を定義でき、さらに Binding までも共通にできる Xamarin.Forms の組み合わせは、今後のモバイルアプリケーション開発をとても効率的にしてくれます、 そしてなにより楽しい!

 今回のサンプルプログラムは

 に置きましたので、是非試してみてください。

ReactiveProperty

Xamarin.Forms