読者です 読者をやめる 読者になる 読者になる

みかづきメモ

学習したことのメモとか、日記とか、備忘録。

Xamarin.Forms + Prism で NavigationPage を使う

C# Xamarin

Xamarin.Forms の NavigationPage を、 Prism で良い感じに使ってみます。

NavigationPage ってなんぞや?というのはこれをみてね。

NavigationPage Class - Xamarin


基本的な構造は、下の記事を参考にしました。

matatabi-ux.hateblo.jp

ただ、 Prism のメジャーバージョンの違いでいろいろ変わっていますが、
だいたい同じ感じでいけます。

NavigationService.cs

public class NavigationService : INavigationService
{
    public static NavigationPage RootPage { get; private set; }

    /// <summary>
    /// Navigates to the most recent entry in the back navigation history by popping the calling Page off the navigation stack.
    /// </summary>
    /// <param name="useModalNavigation">If <c>true</c> uses PopModalAsync, if <c>false</c> uses PopAsync</param>
    /// <param name="animated">If <c>true</c> the transition is animated, if <c>false</c> there is no animation on transition.</param>
    public void GoBack(bool useModalNavigation = true, bool animated = true)
    {
        RootPage.PopAsync();
        PrepareNavigation(RootPage.CurrentPage, null);
    }

    /// <summary>
    /// Initiates navigation to the target specified by the <typeparamref name="T" />.
    /// </summary>
    /// <typeparam name="T">The type which will be used to identify the name of the navigation target.</typeparam>
    /// <param name="parameters">The navigation parameters</param>
    /// <param name="useModalNavigation">If <c>true</c> uses PopModalAsync, if <c>false</c> uses PopAsync</param>
    /// <param name="animated">If <c>true</c> the transition is animated, if <c>false</c> there is no animation on transition.</param>
    public void Navigate<T>(NavigationParameters parameters = null, bool useModalNavigation = true, bool animated = true)
    {
        var page = Activator.CreateInstance(typeof (T)) as Page;
        if (page == null)
            throw new InvalidCastException("T cannot cast to Xamarin.Forms.Page.");
        RootPage.PushAsync(page, animated);
        PrepareNavigation(page, parameters);
    }

    /// <summary>
    /// Initiates navigation to the target specified by the <paramref name="name" />.
    /// </summary>
    /// <param name="name">The name of the target to navigate to.</param>
    /// <param name="parameters">The navigation parameters</param>
    /// <param name="useModalNavigation">If <c>true</c> uses PopModalAsync, if <c>false</c> uses PopAsync</param>
    /// <param name="animated">If <c>true</c> the transition is animated, if <c>false</c> there is no animation on transition.</param>
    public void Navigate(string name, NavigationParameters parameters = null, bool useModalNavigation = true, bool animated = true)
    {
        var type = Type.GetType(name);
        var page = Activator.CreateInstance(type) as Page;
        if (page == null)
            throw new InvalidCastException("T cannot cast to Xamarin.Forms.Page.");
        RootPage.PushAsync(page, animated);
        PrepareNavigation(page, parameters);
    }

    public void GoHome(bool animated = true)
    {
        RootPage.PopToRootAsync(animated);
        PrepareNavigation(RootPage.CurrentPage, null);
    }

    public static void Configure(NavigationPage rootPage)
    {
        RootPage = rootPage;
    }

    private static void PrepareNavigation(Page page, NavigationParameters parameters)
    {
        var navigationAware = page.BindingContext as INavigationAware;
        navigationAware?.OnNavigatedTo(parameters);

        EventHandler onDisappearing = null;
        onDisappearing = (sender, e) =>
        {
            page.Disappearing -= onDisappearing;
            navigationAware?.OnNavigatedFrom(null);
        };
        page.Disappearing += onDisappearing;
    }
}

ViewModel の状態復元などは未実装です。そのうちします。

次は App クラス。
Xamarin.iOS や Xamarin.AndroidLoadApplication とかで呼び出されるクラスです。

public class App : Application
{
    public App()
    {
        var bs = new Bootstrapper();
        bs.Run(this);
    }
}

とりあえず、必要最低限としてこんな感じでいいんじゃないかなと。

そして、 Bootstrapper

public class Bootstrapper : UnityBootstrapper
{
    /// <summary>
    /// Creates the root <see cref="T:Xamarin.Forms.Page" /> for the application.
    /// </summary>
    /// <returns>
    /// The <see cref="T:Xamarin.Forms.Page" />
    /// </returns>
    protected override Page CreateMainPage()
    {
        return NavigationService.RootPage;
    }

    /// <summary>
    /// Used to register types with the container that will be used by your application.
    /// </summary>
    protected override void RegisterTypes()
    {
        Container.RegisterType<INavigationService, NavigationService>(new ContainerControlledLifetimeManager());
        NavigationService.Configure(new RootPage());
    }

    /// <summary>
    /// Configures the <see cref="T:Prism.Mvvm.ViewModelLocator" /> used by Prism.
    /// </summary>
    protected override void ConfigureViewModelLocator()
    {
        ViewModelLocationProvider.SetDefaultViewModelFactory(type => Container.Resolve(type));
    }
}

ポイントとして、ConfigureViewModelLocator をオーバーライドしていることです。
こいつをオーバーライドせずにいると、Prism.Froms.Xamarin に標準でもっている PageNavigationService が使われます。

Prism/PageNavigationService.cs at master · PrismLibrary/Prism · GitHub

これで、自前の NavigationService を使うことができます。

余談ですが、この記事を書いている最中に、わざわざ自前のものを使う必要はないんじゃないかと思いましたが、
気が付かなかったことにしようとおもいます。
(始めから用意されていることに、気がつかないことがおおいです。)