nuits.jp blog

C#, Xamarin, WPFを中心に書いています。Microsoft MVP for Development Technologies。

Prism for Xamarin.Forms入門 Hello, Prism.

前回は、Prism.Formsのイントロダクション的なエントリーを記載しました。

nuits.hatenadiary.jp

今回は実際にPrism.Formsを利用したアプリケーションをテンプレートから作成し、作成されたアプリケーションが、どのように動作しているのか解説してみたいと思います。

なお本エントリーは連載記事「Prism for Xamarin.Forms入門」の一部となっております。
以下に目次がありますので、他のエントリーもご覧いただけると嬉しいです。 【Xamarin】Prism.Forms入門 目次 - nuits.jp blog

はじめに

Prism.Formsを適用したアプリケーションを開発する場合、主に以下の二つの方法があります。

  1. 既存のプロジェクトに対してPrism.Formsを適用する
  2. テンプレートを利用し、Prism.Formsが適用されたプロジェクトを新たに作成する

今回は2.の方法で行きたいと思います。
1.については、機会をみてそのうち書けたらな~と思います。思います!

前提条件

今回のエントリーは以下の環境を前提としております。

  • Windows
    • Windows 10 Pro(Anniversary Updateは未適用。理由は特になし)
    • Visual Studio 2015 Update 3
    • Xamarin 4.1.2.18
    • Prism Template Pack 1.5
  • Mac
    • OS X EI Capitan 10.11.6
    • Xcode 7.3
    • Xamarin Studio 6.0.2

iOSの開発を行わないのであればMacは不要です。
逆にiOSの開発を行うには、実質的にはMacが必須です。
これはひとえにAppleの規約の制限なので、恨み言はAppleまでどうぞよろしくお願いします。

Quickstart

以下の手順で実施します。

  1. Prism Template Packのインストール
  2. Prism.Formsプロジェクトの作成
  3. ビルド
  4. 実行

今回はコードの修正とかは行いません。
それでは行ってみましょう!

Prism Template Packのインストール

Prism Template Packは、Prism開発者のBrian Lagunas氏の公開している、プロジェクトなどの作成テンプレートパックです。
今回は、テンプレートからプロジェクトを作成するため、まずは表題のものをインストールします。

まずはVisual Studioを起動し、「ツール」->「拡張機能と更新プログラム」を選択してください。

f:id:nuitsjp:20160811172604p:plain

「拡張機能と更新プログラム」ダイアログが開かれたら、「オンライン」を選択し、右上の検索ボックスに「prism」と入力して検索してください。
検索結果の中から「Prism Template Pack」を選択し、「ダウンロード」することでインストールが開始されます。

f:id:nuitsjp:20160811172856p:plain

インストールが完了したら、画面の指示に従ってVisual Studioを再起動します。
以上でPrism Template Packのインストールは完了です。

f:id:nuitsjp:20160811173010p:plain

Prism.Formsプロジェクトの作成

Visual Studioが再起動されたら、「ファイル」->「新規作成」->「プロジェクト」を選択し、「新しいプロジェクト」ダイアログを開いてください。

f:id:nuitsjp:20160811173506p:plain

上の画面で、以下の手順でプロジェクトを作成します。

  1. 「インストール済み」->「テンプレート」->「Prism」を開く
  2. Prism Unity App(Forms)を選択
  3. プロジェクト名を決定し(今回はHelloPrismにしています)
  4. 「OK」ボタンを押下

上記を実施すると、プロジェクトの作成が始まります。
昔から重かったのですが、VSのUpdate 3からさらに重くなったような?
まぁ5プラットフォーム分のプロジェクトが作成されるので、ある程度仕方がないのかもしれません。

しばらくすると、以下のようなダイアログが表示されます。

f:id:nuitsjp:20160811173848p:plain

ここではUWPのプロジェクトにおいて、ターゲットとするWindowsのバージョンと、サポートする最も古いバージョンを指定します。
そのままOKしちゃっても良いのですが、Aniversary Updateを指定すると、特定の環境でエラーが発生することがあるようなので、Aniversary Updateが選ばれているようであれば、変更しておいた方がよいかもしれません。そのままでもうまくいくかもしれませんが。
私はうまくいっちゃいました。

以上でプロジェクトの作成は完了です。

ビルド

それではビルドしましょう。
特段、特別な手順は必要ありませんが、標準の設定だと以下の図のようにiOSとUWPがビルドと実行の対象になっていません。

f:id:nuitsjp:20160811174535p:plain

各自の必要に応じて状態を変更してください。
iOS以外はデバックするには「配備」にチェックを付ける必要がありますので注意してください。

それではおもむろにビルドしましょう。
特別問題なくビルドは通るはずですが、何か問題があるようであれば以下のリンク先などを確認してみてください。
答えがあるかもしれません。

nuits.hatenadiary.jp

ytabuchi.hatenablog.com

実行

ビルドが完了したら、実行しましょう。
ここでは詳しく記載しませんが、無事起動すれば以下のように表示されるはずです。

f:id:nuitsjp:20160811175053p:plain

iOS Simulator for Windows最高!

Deep Dive

それでは、作成された中を順次見ていきます。

Solution構成

通常のプロジェクトと比較して、ソリューション構成に大きな違いはありません。

f:id:nuitsjp:20160811175818p:plain

一番上に、全プラットフォームから共有されるPortable Class Library(PCL)のプロジェクトがあり、その下に各プラットフォームのプロジェクトが存在します。
特段説明するようなこともありません。

プラットフォーム別の起動処理

さて、それでは具体的な実装内容を見ていきましょう。
まずはアプリケーションの起動処理です。
起点は各プラットフォームごとに異なりますが、ここではiOSのものを見てみたいと思います。
やや異なりますが、他のプラットフォームでもほぼ同一の流れになっています。

まずはHelloPrism.iOSプロジェクトを開き、最初の起点となるAppDelegate.csを開いてみましょう。

そしてこちらが通常のXamarin.Formsのテンプレートで作成されたAppDelegate.csです。

Appクラスのインスタンスのインスタンスを作成し、そのインスタンスを指定してアプリケーションを起動しています。

ほとんど同じですが、Appクラスのコンストラクタ呼び出し時に、iOSInitializerが渡されている点が異なります。
iOSInitializerクラスは、IPlatformInitializerインターフェースを実装しており、RegisterTypesメソッドが定義されています。
RegisterTypesメソッドでは、DIを利用する際に、プラットフォーム毎に異なる実装を挿入したい場合に、挿入するオブジェクトを指定するために利用します。
DIに関してご存じない方は、別途DIについて詳細を解説する回を設けますので、現時点では「プラットフォームごとの実装の切り替えに利用する」とご理解ください。

プラットフォーム共通の起動処理とトップ画面への画面遷移

続いてAppクラスの中身を見ていきます。
AppクラスはHelloPrismプロイジェクトの中に存在し、全プラットフォームで共通になります。
プラットフォーム共通の初期化処理が記載されています。

App.xaml.csを開いてください。
以下がそのコードです。

そして以下が通常のXamarin.Formsのテンプレートから作成されるAppクラスの一部です。

結構違います。そもそもXAML形式と、通常のcs形式ですし。
さて、一番重要なのは、親クラスが異なることです。
通常は、Xamarin.Forms.Applicationクラスを継承して作成されていますが、PrismではPrism.Unity.PrismApplicationsを継承して作成されています。

Prismのコンストラクタを見てみましょう。

public App(IPlatformInitializer initializer = null) : base(initializer) { }

通常のXamarin.FormsのAppクラスでは、画面の生成までコンストラクタ内で行っていますが、PrismではIPlatformInitializerを引数にとり、親クラスにそれをそのまま受け渡しているだけの実装になっています。

インスタンスの生成後、フレームワーク(Prism)側から、次の順でメソッドが呼び出されます。

  1. RegisterTypes
  2. OnInitialized

RegisterTypesは、先ほどのAppDelegateにも同名のメソッドがありました。
カンの鋭いかたはもうお気づきだと思いますが、DIコンテナに対する初期化処理のうち、プラットフォーム共有の初期化ロジックを記載します。
こう言った、どこで何の処理を記載すればいいのか「Guidline」が存在し、それは多くのベスト「Practices」から成り立っている事が、Prismの強みの一つでもあります。

        protected override void RegisterTypes()
        {
            Container.RegisterTypeForNavigation<MainPage>();
        }

テンプレートで作成されたコードでは、MainPageが「Navigation可能な」クラスとして登録されています。
このとき、標準では登録された画面のクラス名が画面遷移時の名称として利用されますが、個別に指定することも可能です。
この辺の詳細は、Navigationの詳細説明回で詳しく掘り下げたいと思います。

そしてPrism(DIコンテナ含む)の初期化が全て完了したら、OnInitializedメソッドが呼び出されます。

        protected override void OnInitialized()
        {
            InitializeComponent();
            NavigationService.NavigateAsync("MainPage?title=Hello%20from%20Xamarin.Forms");
        }

InitializeComponent()で、XAML側の記載内容の初期化が行われます。が、今回は特に記載が無いため何も行われません。
通常は、画面などから利用される共通のリソース類を定義しておくのに利用します。

続いて、NavigationServiceを利用して、画面遷移を指示しています。
なんかURLぽいですよね?
上記の指定では
「MainPage画面へ遷移しなさい。その際に「title」という名前で「Hello from Xamarin.Forms」という値をパラメータとして渡しなさい」
と指定しています。
ここではパラメータがクエリー文字列的に渡されていますが、以下のように指定することも可能ですし、当然文字列だけではなくオブジェクトも渡せます。

            var parameters = new NavigationParameters();
            parameters["title"] = "Hello from Xamarin.Forms";
            NavigationService.NavigateAsync("MainPage", parameters);

上のURL的な画面遷移に拒否感を覚える方もいらっしゃるかと思いますが、これには色々な理由があります。
詳細は、Navigationの詳細説明回で説明したいと思いますが、アプリケーション外からアプリケーション内へのディープリンクの機構を、通常の画面遷移と同列で扱うことができるなど、Prism.Formsの非常に大きな強みの一つでもあります。

以上で、プラットフォーム共通の起動処理が終了です。
続いて、ViewとViewModelのマッピングを見てみましょう。

ViewとViewModelのマッピング

Prism.Formsでは、ViewのBindingContextへViewModelを設定する処理を、フレームワーク側で自動的に行うことができます。
それでは「HelloPrism」プロジェクト->「Views」フォルダを開き、中のMainPage.xamlを開いてください。

最も重要なのは以下の2行です。

             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"

この2行で、Prism側からViewのBindingContextに対して、適切なViewModelの割り当てが自動的に行われます。
デフォルトではViewのクラス名に「ViewModel」を付加した名前のクラスが自動的に割り当てられます。
ここの命名規則はカスタマイズ可能です。
詳細はViewModelLocatorの詳細回かどこか?で説明したいと思います。

ところでこれ、大したことじゃないように思えますよね?
ですが、実はこれが非常に重要なんです。

Prismでは、各クラス間を疎結合に保つため、DIを利用しています。
そのため、ViewからModelまで一貫したルールで疎結合を保つには、以下のようなコードやコードビハインドでVMのコンストラクタ呼び出しはあってはならないのです。

  <ContentPage.BindingContext>
    <viewModels:MainPageViewModel/>
  </ContentPage.BindingContext>

このあたりの詳細は、DIの詳細回を設けて説明したいと思います。

さて、これで一先ずViewの初期化が確認できました。
次はViewModelを見てみましょう。

画面遷移時のパラメータの授受

App.xaml.csから画面遷移する際に、titleという名前のパラメータを受け渡していたのを覚えていらっしゃるでしょうか?
本節では、画面遷移間のパラメータの授受について説明いたします。

それでは「HelloPrism」プロジェクト->「ViewModels」フォルダを開き、中のMainPageViewModel.csを開いてください。
まずはクラスの宣言を確認してみましょう。

public class MainPageViewModel : BindableBase, INavigationAware

BindableBaseは、INotifayPropertyChangedインターフェースのデフォルト実装です。
ここでは詳細は割愛します。

注目してほしいのはINavigationAwareです。
これは、必ずしもViewModelで実装する必要はありませんが、画面遷移で引数を渡したい、もしくは受け取りたい場合などに実装します。
画面から出る際にリソースを開放したい場合などもこれを利用するのが良いと思います。

INavigationAwareには二つのメソッドが定義されています。

  • OnNavigatedTo
  • OnNavigatedFrom

画面遷移してきた際にOnNavigatedToが呼び出され、画面遷移する前にOnNavigatedFromが呼び出されます。

今回は、画面遷移時のパラメータの受け取りを行いたいのでOnNavigatedToを利用します。
それでは該当箇所を見てみましょう。

        public void OnNavigatedTo(NavigationParameters parameters)
        {
            if (parameters.ContainsKey("title"))
                Title = (string)parameters["title"] + " and Prism";
        }

見たらわかりますねw
こういうわけです。

総括

Prismのテンプレートから作成されただけのアプリケーションですが、多くのエッセンスが含まれていることが理解いただけたかと思います。
そしてアプリケーションを作る際に、最も基本的な範囲はこのテンプレートアプリに含まれています。
まぁDI絡みはほとんど触れられていませんが。

というわけで、駆け足で流れてきましたが、だいたいPrismがどんな物かイメージが付いたでしょうか?
個々のエッセンスの詳細については、また後日個別のエントリーを上げたいと思います。

というわけで今日はここまで。
それではまた!