かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Prism.Forms でプラットフォーム固有処理を呼び分ける方法

Xamarin.Forms にも標準で DependencyService という機能があるんですが、こいつよりも柔軟な素敵な機能が Prism.Forms には提供されています。

IPlatformInitializer

それは IPlatformInitializer です!これは、Prism.Forms のDIコンテナへのインスタンスの登録処理をプラットフォーム固有プロジェクトで実行する箇所を提供するという仕組みになります。

これによって、インターフェースをPCLプロジェクトに定義して、プラットフォーム固有処理をDroidとiOSとUWPプロジェクトに実装したものをDIコンテナに登録するといったことが出来るようになってます。

使ってみよう

ということで使ってみようと思います。Visual Studio 2017でPrism Template Packを入れてプロジェクトを新規作成するとIPlatformInitializerが使われた状態のコードが生成されるので楽です。

今回は Autofac を使ったプロジェクトテンプレートを前提にお話ししますが、他のDIコンテナでも大体同じになります。

プロジェクトを新規作成すると、Droid, iOS, UWPプロジェクトに以下のIPlatformInitializerを実装したクラスが生成されています。

  • Androidプロジェクト
    • MainActivity.csの中のAndroidInitializerクラス
  • iOSプロジェクト
    • AppDelegate.csの中のiOSInitializerクラス
  • UWPプロジェクト
    • MainPage.xaml.csの中のUwpInitializerクラス

こんな感じのクラスになっていると思います。(Androidのものを抜粋してます)

public class AndroidInitializer : IPlatformInitializer
{
    public void RegisterTypes(IContainer container)
    {

    }
}

このRegisterTypesメソッドがPrismのアプリが開始するタイミングで呼び出されるので、ここにコンテナへの登録処理を行います。

本来はOSごとに違う処理をしたいときとかに使うのですが、今回はOSごとに違う文字列を返すという処理を例に作ってみようと思います。(OSごとに違う値を返したいというケースでは本来OnPlatformを使いましょう)

まず、PCLのプロジェクトにプラットフォーム固有の処理を抽象化したインターフェースを定義します。今回の例の場合は、文字列を返すだけのメソッドになります。こんな感じのコードをPCLのプロジェクトに追加します。

namespace PrismAutofacApp3.Services
{
    public interface IGreetingService
    {
        string GetText();
    }
}

そして、プラットフォーム固有のプロジェクトに以下のようにインターフェースを実装したクラスを作成します。

Droidプロジェクト

using PrismAutofacApp3.Services;

namespace PrismAutofacApp3.Droid.Services
{
    public class GreetingService : IGreetingService
    {
        public string GetText()
        {
            return "Hello Droid project.";
        }
    }
}

iOSプロジェクト

using PrismAutofacApp3.Services;

namespace PrismAutofacApp3.iOS.Services
{
    public class GreetingService : IGreetingService
    {
        public string GetText()
        {
            return "Hello iOS project.";
        }
    }
}

UWPプロジェクト

using PrismAutofacApp3.Services;

namespace PrismAutofacApp3.UWP.Services
{
    public class GreetingService : IGreetingService
    {
        public string GetText()
        {
            return "Hello UWP project.";
        }
    }
}

PlatformInitializer へのインスタンス登録処理の追加

Droidプロジェクト

public class AndroidInitializer : IPlatformInitializer
{
    public void RegisterTypes(IContainer container)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<GreetingService>().As<IGreetingService>().SingleInstance();
        builder.Update(container);
    }
}

iOSプロジェクト

public class iOSInitializer : IPlatformInitializer
{
    public void RegisterTypes(IContainer container)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<GreetingService>().As<IGreetingService>().SingleInstance();
        builder.Update(container);
    }
}

UWPプロジェクト

public class UwpInitializer : IPlatformInitializer
{
    public void RegisterTypes(IContainer container)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<GreetingService>().As<IGreetingService>().SingleInstance();
        builder.Update(container);
    }
}

使ってみよう

使い方は、普通にDIしたクラスと同じです。Prism Template Packで生成さらたプロジェクトをちょっといじってみましょう。MainPageViewModelにIGreetingServiceをDIして使っています。

using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using PrismAutofacApp3.Services;
using System;
using System.Collections.Generic;
using System.Linq;

namespace PrismAutofacApp3.ViewModels
{
    public class MainPageViewModel : BindableBase, INavigationAware
    {
        private IGreetingService GreetingService { get; }

        private string _title;
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        public MainPageViewModel(IGreetingService greetingService)
        {
            this.GreetingService = greetingService;
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {
            this.Title = this.GreetingService.GetText();
        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
        }
    }
}

実行して確認してみましょう。今Macがない環境なのでAndroidとUWPだけですが以下のように表示されるテキストが変わっていることが確認できます。

f:id:okazuki:20170615203023p:plain

f:id:okazuki:20170615203204p:plain

まとめ

ということでIPlatformInitializerについて紹介しました。こいつのメリットとして、プラットフォーム固有のクラスに対して、さらに別のクラスをDIしたりといったこともできるという柔軟性は、DependencyServiceに無いですし、このようにインターフェースをDIするようにしておくと、後で単体テストするときでもいい感じにモックに差し替えたりできるので便利です。

ということで良いXamarin.Froms + Prism.Forms生活を!!