Xamarin.Forms は、Xamarin に新たに搭載されたクロスプラットフォームUIフレームワーク&MVVMフレームワークです。
ReactiveProperty は、MVVMの(特に ViewModelの)実装を強力にサポートしてくれる、Reactive Extensions を基盤としたライブラリです。
両者を組み合わせると、Android/iOSアプリが COOL な感じで書けるんじゃないか、という事で試してみました。
Mac + Xamarin Studio を使いますが、Windows + Visual Studio + Xamarin-Addin でもイケると思います。
新規ソリューションを、[C#]−[Mobile Apps]−[Blank App(Xamarin.Forms Portable)]で作成します。
作成されたソリューションの一番上にあるプロジェクト(.Android とか .iOS が付いていないやつ)のプロジェクト設定を開いて Profile を PCL 4.5 - Profile49 に変更します。元々の Profile78 では ReactiveProperty が Nuget からインストールできないためです。最近のプラットフォームを対象にするなら、あまり影響はなさそうです。
メニューの[プロジェクト]ー[Add Packages]で Nuget のダイアログを開き、図のように 「Reactive Extensions - Main Library」と「ReactiveProperty Portable」を追加します。
(Reactive Extensions の追加の際、なにやらWarningが出るようですが、とりあえず進めます。)
PCL のプロジェクトに、FirstViewModel.cs
を作成します。
FirstViewModel
は、以下のようなプロパティとコマンドを持ちます。
これらの実装が下のようになります。
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 の機能により、流れるように記述できます。
画面(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 の付いたプロジェクトをスタートアップにして、実行します。
実機で動作確認するの忘れてました(実機はAOTなのに対してiOSシミュレータはJITなのでリフレクションとかが普通に動いてしまう)。 実機でも問題なく動作しました!
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();
}
}
Reactive Extensions のメリットを活かして MVVM を構築できる ReactiveProperty と、ワンソースで Android/iOS の画面を定義でき、さらに Binding までも共通にできる Xamarin.Forms の組み合わせは、今後のモバイルアプリケーション開発をとても効率的にしてくれます、 そしてなにより楽しい!
今回のサンプルプログラムは
に置きましたので、是非試してみてください。