Xamarin 用の Injection フレームワーク Saitama を使ってみた

NuGet のフィードを眺めていたら、Saitama.Android というフレームワークが更新されたという情報が流れてきました。

どういう経緯でこの名前がついたのかわかりませんが、日本人的にはすぐにこの AA を思い浮かべるでしょう。*1

f:id:iseebi:20160406064936g:plain

名前はともかく、紹介記事を読んでみると、MvvmCross がつらいけど MvvmCross のインジェクションっぽいのが欲しくて作ったみたいなことが書いてあって気になったので試してみました。

Saitama フレームワークの概要

任意の DI コンテナをバックエンドとして Xamarin.iOS / Xamarin.Android での開発で便利な形の依存性注入機能を追加するフレームワークです。あくまで注入するところのサポートを担うだけなので、DI コンテナ自体は別途必要になります。公式では Autofac の対応が公開されています。

Saitama フレームワークは以下の NuGet パッケージから構成されています。PCL では Core と Autofac、プラットフォーム固有ではそれに加えてプラットフォーム固有分を参照すると良いでしょう。

  • Saitama.Core
  • Saitama.Apple
  • Saitama.Android
  • Saitama.Autofac

Saitama を使う

コンテナに登録するクラスを作る

クラスの作り方としては、MvvmCross と同じで、インターフェイスを用意してそれを実装します。

たとえば、PCL でこういうインターフェイスを定義して

using System;

namespace SaitamaDemo
{
    public interface IDeviceInfo
    {
        string Name { get; }
    }
}

iOS 固有側はこういう実装

using UIKit;

namespace SaitamaDemo.iOS
{
    public class DeviceInfo : IDeviceInfo
    {
        public string Name {
            get {
                return UIDevice.CurrentDevice.SystemName;
            }
        }
    }
}

Android 固有ではこういう形。

using Android.OS;

namespace SaitamaDemo.Droid
{
    public class DeviceInfo : IDeviceInfo
    {
        public string Name {
            get { return Build.Model; }
        }
    }
}

起動コードの実装

iOS では AppDelegate を SaitamaApplicationDelegate のサブクラスに変更、Android では SaitamaApplication のサブクラスで Application クラスを作ります。

これらは GetInjectionContainer というメソッドを実装する必要があり、この中で InjectionContainerResolverBase を返します。Saitama.Autofac を使う場合はこのような実装になります。

protected override InjectionContainerResolverBase GetInjectionContainerResolver ()
{
    var builder = new ContainerBuilder ();
    builder.RegisterType<DeviceInfo> ().As<IDeviceInfo> ();

    return new AutofacInjectionContainerResolver (builder, Assembly.GetExecutingAssembly ());
}

利用箇所の実装

Saitama.Apple および Saitama.Android では ViewController や Activity などといった画面ベースクラスに対応するカスタムクラスが入っており、Saitama を使う画面ではこれらをベースクラスとします。

iOS では元が UIViewController であれば SaitamaViewController にします。あとは使いたいインターフェイスをパブリックで公開していれば、インスタンス生成時に注入されます。

using System;
using Saitama.Apple.Controllers;
using UIKit;

namespace SaitamaDemo.iOS
{
    public partial class ViewController : SaitamaViewController
    {
        public IDeviceInfo DeviceInfo { get; set; }

        public ViewController (IntPtr handle) : base (handle)
        {
        }

        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();
            Button.TouchUpInside += delegate {
                Button.SetTitle (DeviceInfo.Name, UIControlState.Normal);
            };
        }
    }
}

Android では元が Activity であれば SaitamaActivity となります。

using Android.App;
using Android.Widget;
using Android.OS;
using Saitama.Android.Activities;

namespace SaitamaDemo.Droid
{
    [Activity (Label = "SaitamaDemo", MainLauncher = true, Icon = "@mipmap/icon")]
    public class MainActivity : SaitamaActivity
    {
        public IDeviceInfo DeviceInfo { get; set; }

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

            SetContentView (Resource.Layout.Main);

            var button = FindViewById<Button> (Resource.Id.myButton);
            button.Click += delegate { button.Text = DeviceInfo.Name; };
        }
    }
}

問題点

もしかすると何か忘れているのかもしれませんが、ネストした注入ができませんでした。つまりこういうコードを書いて、View 側に public IMainViewModel ViewModel { get; set; } としても、MainViewModel.DeviceInfo に注入されませんでした。

using System;
namespace SaitamaDemo
{
    public interface IMainViewModel
    {
        IDeviceInfo DeviceInfo { get; set; }
        string Name { get; }
    }

    public class MainViewModel : IMainViewModel
    {
        public IDeviceInfo DeviceInfo { get; set; }
        public string Name {
            get { return DeviceInfo.Name; }
        }
    }
}

あと、Application クラスを継承してあれこれする系統のフレームワークを別途入れることができなくなるので、ここもデメリットでしょう。


MvvmCross は確かに便利なのですが、いかんせん高機能すぎる面はあるかと思うので、こういった機能を切り出したフレームワークは今後も出てきそうですね。

参考

*1:その後 @atsushieno さんに指摘いただいたのですが、ワンパンマンのほうじゃないかとのこと。そういえばあっちもサイタマだった…。