kamulog

xamarin.formsのネタなど

Prism.Forms 6.3.0 から 7.0.0 へのマイグレーションに関するメモ

Prism.Forms 7.0.0 のリリースから結構経ってしまいましたが、ようやく更新に手がつけられたので、6.3.0からの更新方法などのメモを残しておきます。

DIコンテナの抽象化

DIコンテナの種類に依存するコードを直接書かずに抽象化されたIContainerRegistryを使うようになりました。これによってどのDIコンテナを使ってもその種類を意識することなくコードが書けて、またコンテナを変更した際の影響が少なくなります。

protected override void RegisterTypes()
{
    Container.RegisterForNavigation<TestPage>();
}


protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterForNavigation<TestPage>();
}

その他のクラスの登録

旧(Autofacの例)

protected override void RegisterTypes()
{
    var containerUpdater = new ContainerBuilder();
    containerUpdater.RegisterSource(new Autofac.Features.ResolveAnything.AnyConcreteTypeNotAlreadyRegisteredSource());

    containerUpdater.RegisterType<HogeModel>().As<IHogeModel>();
    containerUpdater.Update(this.Container);
}

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.Register<IHogeModel, HogeModel>();

    // prismに用意されてないような登録方法はAutofac本体のbuilderを使用する
    // 個別のDIの機能は拡張メソッドで取得可能
    var builder = containerRegistry.GetBuilder();
    
    builder.RegisterType<FugaModel>().As<IFugaModel>().PropertiesAutowired();
}

基本はIContainerRegistryで登録できるのですが、特殊な登録が必要な場合は固有のDIの機能を使わなければなりません。その場合はIContainerRegistryの拡張メソッドにContainerなどを取得できるものがあるので、そちらを使います。

ちなみにAutofacのRegisterSourceやBuildやはprismがRegisterTypesの後にやってくれるのでユーザーが記述する必要はありません。ということはRegisterTypesで全ての型を登録しておけよってことだと思います。

IPlatformInitializer

こちらもIContainerRegistryを通して登録するように変わっています。

public class AndroidInitializer : Prism.IPlatformInitializer
{
    public void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.Register<INativeHoge, NativeHoge>();
    }
}

カスタムNavigationServiceの登録方法

NavigationServiceをカスタマイズしている方も多いと思いますが、その場合の差し替え方法も変わっています。 方法はいろいろあると思いますが一例です。

従来
App.xaml.cs

protected override INavigationService CreateNavigationService()
{
    return Container.ResolveNamed<IMyNavigationService>("MyPageNavigationService");
}

protected override void ConfigureContainer()
{
    base.ConfigureContainer();

    var builder = new ContainerBuilder();
    builder.Register(ctx => new MyPageNavigationService(Container, Container.Resolve<IApplicationProvider>(), Container.Resolve<ILoggerFacade>())).Named<IMyNavigationService>("MyPageNavigationService");
    builder.Update(this.Container);
}

}
protected override void ConfigureViewModelLocator()
{
    ViewModelLocationProvider.SetDefaultViewModelFactory((view, type) => {
        NamedParameter parameter = null;

        var page = view as Page;
        if (page != null) {
            parameter = new NamedParameter("navigationService", CreateNavigationService(page));
        }

        return Container.Resolve(type, parameter);
    });
}


public partial class App : PrismApplication
{
    public const string MyNavigationServiceName = "MyPageNavigationService";

    protected override void RegisterRequiredTypes(IContainerRegistry containerRegistry)
    {
        base.RegisterRequiredTypes(containerRegistry);

        containerRegistry.Register<IMyNavigationService, MyPageNavigationService>(MyNavigationServiceName);
    }

    protected override void ConfigureViewModelLocator()
    {
        ViewModelLocationProvider.SetDefaultViewModelFactory((view, type) => {

            return Container.ResolveViewModelForView(view,type);
        });
    }
}
using System;
using Autofac;
using Sample.NavigationService;
using Prism.Common;
using Prism.Autofac;
using Xamarin.Forms;

namespace Prism.Ioc
{
    public static class IContainerProviderExtensions
    {
        public static IMyNavigationService CreateNavigationService(this IContainerProvider container, Page page)
        {
            var navigationService = container.Resolve<IMyNavigationService>(App.MyNavigationServiceName);
            ((IPageAware)navigationService).Page = page;
            return navigationService;
        }

        public static object ResolveViewModelForView(this IContainerProvider container,object view,Type viewModelType)
        {
            NamedParameter parameter = null;
            if (view is Page page) {
                parameter = new NamedParameter(PrismApplicationBase.NavigationServiceParameterName, container.CreateNavigationService(page));
            }

            return container.GetContainer().Resolve(viewModelType, parameter);
        }
    }
}

ConfigureContainerとCreateNavigationServiceは廃止になったので、ConfigureContainerに近いRegisterRequiredTypesあたりでカスタムNavigationServiceの登録をします。CreateNavigationServiceは拡張メソッドに移動してオーバーライドできなくなってしまったので似たような拡張メソッドを作成します。ついでにそこにDIコンテナに依存する他の処理も書いて隔離しておきます。

注:IMyNavigationServiceはINavigationServiceを継承したinterfaceとし、MyNavigationServiceはIMyNavigationServiceの実装でPageNavigationServiceを継承したものが存在するとします。

これで各ViewModelのコンストラクタの第一引数の"navigationService"にカスタムのMyNavigationServiceが注入されるようになります。

NavigationService差し替えは6.3.0の方が楽でしたね。7.0.0はちょっと面倒です。DIコンテナ抽象化の影響かと思うので仕方ないですが。

6.3.0の頃のNavigationService時のコードをそのまま使えるようにする(カスタムNavigationService実装例)

7.0.0になって何故かINavigationServiceのGoBackAsyncとNavigateAsyncのアニメーションの有無を指定できる引数のオーバーロードがなくなりました。

もし、6.3.0の時のコードでanimatedを指定するコードが多ければ全部修正しないといけませんし、デフォルトでアニメーションするようになっているので7.0.0では無効にする手段がなくて困ります。

そこで新しい方のINavigationServiceにanimatedを受け取れるものを追加して、実装のPageNavigationServiceの方にも追加します。

IMyNavigationService

public interface IMyNavigationService:INavigationService
    {   
        Task<bool> GoBackAsync(NavigationParameters parameters, bool? useModalNavigation, bool animated = true);
        Task NavigateAsync(string name, NavigationParameters parameters, bool? useModalNavigation, bool animated = true);
    }

MyNavigationService

public class MyPageNavigationService : PageNavigationService, IMyNavigationService
{
    public MyPageNavigationService(
                IContainerExtension container, IApplicationProvider applicationProvider,
                IPageBehaviorFactory pageBehaviorFactory, ILoggerFacade logger
            ) : base(container, applicationProvider,pageBehaviorFactory, logger) { }

    public Task<bool> GoBackAsync(NavigationParameters parameters,bool? useModalNavigation,bool animated = true)
    {
        return GoBackInternal(parameters, useModalNavigation, animated);
    }

    public Task NavigateAsync(string name, NavigationParameters parameters, bool? useModalNavigation, bool animated = true)
    {
        return NavigateInternal(name, parameters, useModalNavigation, animated);
    }
}

これで既存の旧NavigationService使用部分のコードはそのまま使えるようになります。 ですが型をINavigationServiceからIMyNavigationServiceに置換する必要があるのでこれは一括置換で変更すると良いと思います。

このMyPageNavigationServiceに他の独自の処理を追加してIMyPageNavigationServiceの方にも定義を増やしていけば7.0.0でもオレオレNavigationを作成できます。

まとめ

  • DIコンテナ抽象化良い感じ
  • NavigationServiceの差し替えは少し面倒だけど可能
  • 何故animatedオプションを隠した?
  • 7.0.0でもカスタムNavigationServiceは可能
  • 今回検証してないけどTabbedPageの遷移方法が変わったので使っている人は注意