Xamarin Advent Calendar 2013 も最終日となりました。 最後は、「実用的な」マルチプラットフォーム開発のツールを紹介します。
MvvmCross ってやつを紹介したいのですが、まずは事前知識からさらりと。
MVVM は、 Model-View-ViewModel の頭文字を取ったものです。 MVC パターンの派生で、Microsoft が WPF/Silverlight のために作ったそうですが、今では JavaScript の Knockout.js などでも利用されています。
(via Model View ViewModel - WikiPedia CC-BY 3.0)
いやもう絶対他のリソース読んだ方が分かりやすいですから(逃げ)。
WPF, Silverlight, Windows Store App など、異なるプラットフォームでバイナリを共有できる “ポータブルな” クラスライブラリ。
そして、先日、Xamarin.Android と iOS でもポータルクラスライブラリが使える/作れるようになりました。
ポータブルクラスライブラリとして作られた DLL は、WPF でも Store App でも Android でも iOS でも使いまわせる、という事です。
さて本題。
MvvmCross は、様々なプラットフォームに対応した MVVMフレームワークです。対応プラットフォームをざっと挙げると、
です。
MvvmCross を使うと、MVVM パターンでいうところの Model, ViewModel を複数のプラットフォームで共通にできます。
図にすると下のような感じです。
「Model でプラットフォーム固有の機能使いたい場合もあるじゃん?」とかにも対応しているので、これが全てでは無いですが、最初の説明としてはこんなもんです。
ホームページなどはこちら。 Evolve セッションの Slides の 1〜10ページ が分かりやすいですかね。
MvvmCross の現在 Stable なのは「v3」で、その Tutorials が
にあります。これがまたドットインストールも真っ青の充実ぶり。 この動画を順番に見ながら写経すれば使えるようになっちゃいます。
という訳で、最初の1つ「N=0」をトレースしてみましょう。
では、開始〜。
Xamarin Studio にて、新しいソリューションと「Portable Library」プロジェクトを作ります。プロジェクト名は “FirstDemo.Core”、ソリューション名は “FirstDemo” とします。
MvvmCross/MvvmCross-Binaries の v3.1 branch をダウンロードなり Clone なりします。
FirstDemo.Core プロジェクトに、先ほどダウンロードした MvvmCross-Binaries-3.1/VS2012/bin/Release/Mvx/Portable
の中の以下のファイルを参照追加します。
Nuget でインストールされるはずのクラスを実装します。
まず、ViewModels というフォルダを作ってその中に FirstViewModel
クラスを作ります。
ViewModels/FirstViewModel.cs
using System;
using Cirrious.MvvmCross.ViewModels;
namespace FirstDemo.Core.ViewModels
{
public class FirstViewModel : MvxViewModel
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set { _firstName = value; RaisePropertyChanged(() => FullName);}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set { _lastName = value; RaisePropertyChanged(() => FullName);}
}
public string FullName
{
get { return String.Format("{0} {1}", _firstName, _lastName); }
}
}
}
次に App
クラスはルートに。
App.cs
using System;
using Cirrious.CrossCore.IoC;
using FirstDemo.Core.ViewModels;
namespace FirstDemo.Core
{
public class App : Cirrious.MvvmCross.ViewModels.MvxApplication
{
public override void Initialize()
{
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
RegisterAppStart<FirstViewModel>();
}
}
}
MyClass.cs
は使わないので削除します。
ここまでが動画の 4:40 くらいです。
動画では 11:11 から。
ソリューションに Android Application プロジェクトを追加します。名前は “FirstDemo.Droid” とします。
まず、FirstDemo.Core をプロジェクト参照で追加します。 次に、MvvmCross 関連のアセンブリ群、以下を追加します。動画では Nuget でやってるところです。
「*」付きのアセンブリは MvvmCross-Binaries-3.1/VS2012/bin/Release/Mvx/Droid/
から、付いてないアセンブリは Core と同じく MvvmCross-Binaries-3.1/VS2012/bin/Release/Mvx/Portable/
から追加します。後ろに “Droid” と付いているものは、プラットフォーム用のフォルダから持ってきましょうという事ですね。
こちらも Nuget が使えれば〜 のところ。
FirstView
は、MainView.cs をリファクタでリネーム& Views フォルダ移動して内容を下のように書き換えます。
Views/FirstView.cs
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Cirrious.MvvmCross.Droid.Views;
namespace FirstDemo.Droid.Views
{
[Activity(Label = "FirstDemo.Droid", MainLauncher = true, Icon = "@drawable/icon")]
public class FirstView : MvxActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
}
}
}
Setup.cs はルートに作ります。
Setup.cs
using System;
using Cirrious.MvvmCross.Droid.Platform;
using Android.Content;
using Cirrious.MvvmCross.ViewModels;
namespace FirstDemo.Droid
{
public class Setup : MvxAndroidSetup
{
public Setup(Context appContext) : base(appContext) { }
protected override IMvxApplication CreateApp()
{
return new Core.App();
}
}
}
あ、 SplashScreen
は省略します。説明が面倒なので。
これも Nuget の代わりにやるところ。 データバインディングを行うために必要なファイルです。この手順を忘れて、ビルドエラーでしばらくハマってました。
Resource/values
フォルダ内に以下の xml ファイルを作成します。
Resources/values/MvxBindingAttributes.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MvxBinding">
<attr name="MvxBind" format="string"/>
<attr name="MvxLang" format="string"/>
</declare-styleable>
<declare-styleable name="MvxControl">
<attr name="MvxTemplate" format="string"/>
</declare-styleable>
<declare-styleable name="MvxListView">
<attr name="MvxItemTemplate" format="string"/>
<attr name="MvxDropDownItemTemplate" format="string"/>
</declare-styleable>
<item type="id" name="MvxBindingTagUnique"/>
<declare-styleable name="MvxImageView">
<attr name="MvxSource" format="string"/>
</declare-styleable>
</resources>
画面のレイアウトを作ります。 Xamarin Studio の Android用 UI デザイナは強力なので、ここだけは Visual Studio に勝っていると言えます。
Main.axml
を開いて、レイアウトされているボタンを削除し、「Plain Text」を2つと、Text(Mid) を1つ、縦に並べて配置します。
Main.axml
の「ソース」を開いて、データバインディングについての記述をします。完成形は下のようになります。
Resources/layout/Main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
local:MvxBind="Text FirstName" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
local:MvxBind="Text LastName" />
<TextView
android:text="Medium Text"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
local:MvxBind="Text FullName" />
</LinearLayout>
これで Android UI の実装は終わり。動画では 16:20 あたり。 Android 側では、初期化を除けば 何も実装してない のがミソ。
エミュレータで実行してみると、こんな感じです。
Steve を痛恨のスペルミスしたけど、まあいいや。
では続いて、iOS アプリの実装です。
動画では 25:58 から。
ソリューションに iPhone Application プロジェクトを追加します。Storyboard を使ってみましょう。名前は “FirstDemo.Touch” とします。(iOS 版の接尾辞に ”Touch” を使うのは、Xamarin.iOS の以前の名称である ”MonoTouch” からだと思いますが、大文字で始まる .NET 文化の中では “iOS” はとかく都合が悪いので、Touch という接尾辞は私も気に入っています。)
Android の場合と同様、FirstDemo.Core をプロジェクト参照で追加します。 次に、MvvmCross 関連のアセンブリ群、以下を追加します。
「*」付きのアセンブリは MvvmCross-Binaries-3.1/VS2012/bin/Release/Mvx/Touch/ から、付いてないアセンブリは Core と同じく MvvmCross-Binaries-3.1/VS2012/bin/Release/Mvx/Portable/ から追加します。後ろに “Touch” と付いているものは、プラットフォーム用のフォルダから持ってきましょうという事ですね。
とここまで書いておいて、MvvmCross は実は Storyboard で使う時は少し細工が必要な事に気づいた。。。
MvvmCross の作者である slodge さんが下で回答されています。
という訳でここからのコードは、上記で示されている ‘eh’ もミックスしたもので、動画とは少し異なります。動作は同じです。
まず、Setup.cs をルートに作成して以下のように実装します。
Setup.cs
using System;
using Cirrious.MvvmCross.Touch.Platform;
using Cirrious.MvvmCross.Touch.Views.Presenters;
using Cirrious.MvvmCross.ViewModels;
namespace FirstDemo.Touch
{
public class Setup : MvxTouchSetup
{
public Setup(MvxApplicationDelegate appDelegate, IMvxTouchViewPresenter presenter)
: base(appDelegate, presenter) { }
protected override IMvxApplication CreateApp()
{
return new Core.App();
}
}
}
既存の AppDelegate.cs を以下のように書き換えます。
AppDelegate.cs
using MonoTouch.Foundation;
using Cirrious.MvvmCross.Touch.Platform;
using MonoTouch.UIKit;
using Cirrious.MvvmCross.Touch.Views.Presenters;
namespace FirstDemo.Touch
{
[Register("AppDelegate")]
public partial class AppDelegate : MvxApplicationDelegate
{
public override UIWindow Window { get; set; }
public override void FinishedLaunching(UIApplication application)
{
var setup = new Setup(this, new MvxTouchViewPresenter(this, Window));
setup.Initialize();
}
}
}
FirstView.cs は、FirstDemo.FirstDemoViewController.cs をリネームして作成します。Views フォルダを作って移動もしましょう。
また、MvxViewConroller
から派生させるように変更します。
iOS 版の画面をレイアウトします。
動画ではコードで UITextField
などを配置していますが、 UI をコードで記述する事は万死に値する ので、Xamarin Studio の iOS デザイナもしくは Xcode の Interface Builder を使います。
下の図は、Xamarin Studio α版の iOS デザイナ を使った例です。以前に紹介したものです。
TextField 2つと、Label を、textEditFirst
, textEditLast
, labelFull
という変数にしておきます。
MvvmCross を iOS で使う場合、残念ながら storyboard 側にバインディングを記述する事はできないので、FirstView.cs
にコードで記述します。
ViewDidLoad
に以下のように追記します。
Views/FirstView.cs
using System;
using Cirrious.MvvmCross.Binding.BindingContext;
using Cirrious.MvvmCross.Touch.Views;
using Cirrious.MvvmCross.ViewModels;
using FirstDemo.Core.ViewModels;
namespace FirstDemo.Touch
{
public partial class FirstView : MvxViewController
{
/* 省略 */
public override void ViewDidLoad()
{
this.Request = new MvxViewModelRequest<FirstViewModel>(
null, null, new MvxRequestedBy());
base.ViewDidLoad();
var set = this.CreateBindingSet<FirstView, FirstViewModel>();
set.Bind(textEditFirst).To(vm => vm.FirstName);
set.Bind(textEditLast).To(vm => vm.LastName);
set.Bind(labelFull).To(vm => vm.FullName);
set.Apply();
}
/* 省略 */
}
}
これで iOS 側も実装終了、動画では 33:40 まで来ました。
動画では、Windows であるため Mac にリモート接続して実行していますが、Mac+Xamarin Studio なら即実行できます。
今日の完成版のコードは
に置いておきました。
ちょっと STEP が多くなっちゃいましたが、MvvmCross を使うと、複数のプラットフォームで ViewModel-Model を共通化できることが分かったと思います。
N+1 Days- は、現在 39(!!) まであります。動画観てるだけでもわかった感じになります。(私は N=8 まで観た気がします。)
また、 @MvvmCross にツイートすると、気さくに(英語ですが)回答してくれます。
使いこなせば強力な武器になる MvvmCross 、今後も要チェックです。
勢いに任せて作った XAC2013、なんとか完走できました。 「全部俺」でもいいやと思っていましたが、私含め5名の方に参加して頂けました、ありがとうございました。
日経ソフトウェア によると、「2014年にブレークする技術」に Xamarin はありませんでしたが、MS と提携以後のプッシュぶりを見ていると、今後、来年のブレークに期待できそうです。
昨日の @atsushieno さんの Xamarin 創立からの苦労話 を知ると、安易に「ライセンス高ぇ!値下げPlz!」などとは言えませんが、ユーザ(デベロッパ)を増やしたいのもまた事実。α/β版だけでも 30days のトライアル期限なくしてもらえたいら嬉しいですね。
Xamarin Advent Calendar 2013 はこれで終わりですが、今後もちまちまと Tips みたいなものを書いてくつもりですので、 Qiita のタグ をチェックしてもらえると嬉しいです。
そして Xamarin Advent Calendar 2014 でまたお会いしましょう。