かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

XamarinのAndroidアプリでReactivePropertyを使う

最近書いてるReactivePropertyですが、Xamarinにも対応しています。なので、簡単にカウントアップするサンプル(ひな形で生成されるやつ)を作ってみようと思います。

Xamarinのバージョン

使用しているXamarinのバージョンは5.0(build784)です。XamarinのVisual Studioプラグインを利用してVisual Studio上で開発しています。

プロジェクトの作成とReactivePropertyのインストール

新規作成から「Android Ice Cream Sandwich Application」を選びます。プロジェクト名は、CounterAppにしました。

NuGetパッケージの管理からReactivePropertyで検索して「ReactiveProperty Portable」をインストールします。最新のXamarinに現時点で対応しているのは、0.4.5-beta3になるので、リリース前のパッケージを含めるを選択してください。

f:id:okazuki:20140507170822j:plain

Reactive ExtensionsとReactivePropertyがインストールされます。

f:id:okazuki:20140507170944j:plain

カウンタークラスの作成

次にModelとなるCounterクラスを作成します。これはINotifyPropertyChangedを実装した普通のC#のクラスです。現在のカウント値を表すプロパティと、カウントアップするメソッドを持っています。

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace CounterApp
{
    public class Counter : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private bool SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
        {
            if (Equals(field, value))
            {
                return false;
            }

            field = value;
            var h = this.PropertyChanged;
            if (h != null)
            {
                h(this, new PropertyChangedEventArgs(propertyName));
            }

            return true;
        }

        private int count;

        public int Count
        {
            get { return this.count; }
            private set { this.SetProperty(ref this.count, value); }
        }

        public void Incr()
        {
            this.Count++;
        }
    }
}

ViewModelの作成

次に、Activityに紐づけるViewModelクラスを作成します。名前はMainViewModelにしました。先ほど作成したCounterクラスを内部にもってActivityに公開するプロパティや操作などをReactivePropertyやReactiveCommandで定義しています。

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

namespace CounterApp
{
    public class MainViewModel
    {
        private readonly Counter Model = new Counter();

        // Activityに公開するカウント値
        public ReactiveProperty<string> CountMessage { get; private set; }

        // インクリメントを行うためのコマンド
        public ReactiveCommand IncrCommand { get; private set; }

        public MainViewModel()
        {
            // model -> rxpropへのOneWayバインディング
            this.CountMessage = this.Model
                .ObserveProperty(c => c.Count)
                .Select(i => string.Format("{0} click!", i))
                .ToReactiveProperty();

            // コマンドが実行されたらModelのIncrメソッドを実行する
            this.IncrCommand = new ReactiveCommand();
            this.IncrCommand.Subscribe(_ => this.Model.Incr());
        }
    }
}

Activityの作成

MainActivityのコードをひな形から修正します。

// バインディング用メソッドが定義されてる名前空間
using Codeplex.Reactive.Binding;

using System;

using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;

namespace CounterApp
{
    [Activity(Label = "CounterApp", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        // ViewModel
        private readonly MainViewModel DataContext = new MainViewModel();

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            // Get our button from the layout resource,
            // and attach an event to it
            var button = FindViewById<Button>(Resource.Id.MyButton);

            // CountMessageをbuttonのTextプロパティへバインド
            this.DataContext.CountMessage.BindTo(button, b => b.Text);

            // コマンドをイベントハンドラに変換してボタンと紐づける
            button.Click += this.DataContext.IncrCommand.ToEventHandler();
        }
    }
}

ReactivePropertyを通常のプロパティにバインドするためのBindToという拡張メソッドが、Codeplex.Reactive.Binding名前空間に用意されているので、それを使ってButtonのTextと紐付けを行います。

コマンドはToEventHandlerという拡張メソッドでイベントハンドラへ変換する拡張メソッドが、同じくCodeplex.Reactive.Binding名前空間に用意されてるので、それを使ってButtonのClickイベントと紐づけます。

実行して動作確認

MonoForAndroid_API_15のエミュレータを起動してじっくりと配備を行います。ここらへん実機がほしくなりますね…。

実行すると以下のような画面が表示されます。

f:id:okazuki:20140507173346j:plain

ボタンを押すとカウントアップされていきます。

f:id:okazuki:20140507173428j:plain

まとめ

簡単なサンプルレベルでしか試していませんが、とりあえず動く…!ということでReactivePropertyをよろしくお願いいたします。