ブランクXamarin.FormsプロジェクトにPrismを適用する
https://xamgirl.com/prism-in-xamarin-forms-step-by-step-part-1
環境
- Windows 10 1909
- Visual Studio 2019 16.4
- Xamarin.Forms 4.4.0
- Prism.Unity.Forms 7.2.0
ブランクアプリの作成
まずはブランクXamarin.Formsプロジェクトを作成します。
インストール済みパッケージの更新
パッケージの更新があればやっておきます。
Prismのインストール
NuGetマネージャでPrismを検索します。
Prism関連のパッケージが表示されますが、この中からPrism.Unity.Forms
を選択します。なぜかというと、これしか知らないからです。
Unity
という文字が入っていますが、ゲーム開発などで有名なUnity
ではありません(お約束)。
App.xamlにPrismの参照を追加
<?xml version="1.0" encoding="utf-8" ?> <prism:PrismApplication xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:prism="clr-namespace:Prism.Unity;assembly=Prism.Unity.Forms" mc:Ignorable="d" x:Class="ApplyPrismToXamarinFormsFromScratch.App"> <Application.Resources> </Application.Resources> </prism:PrismApplication>
追加したのはこれ。
xmlns:prism="clr-namespace:Prism.Unity;assembly=Prism.Unity.Forms"
その上で、Application
要素をprism:PrismApplication
に変更する。
App.xaml.csでPrismApplicationクラスを継承する必要はない
上記サイトだと、App.xaml.cs
内のApp
クラスがPrismApplication
クラスを継承するようにしているが、App.xaml
の変更によって親クラスがPrismApplication
になっているので、ここで継承する必要はありません。
App.xaml.cs
のApp
クラスの親クラスはApp.xaml
のルートクラスになります。
これを確認してみます。
App.xaml.cs
内のApp
クラスにマウスオーバーして右クリックからGo to Definition(定義へ移動?)
を選択すると、移動先の候補がおそらくVisual Studioのウィンドウ下部に表示されるので、App.xaml.g.cs
を選択してジャンプします。
この中でApp
クラスに関する定義があるので見てみると、以下のようにPrismApplication
クラスを継承するようになっているのがわかります。
public partial class App : global::Prism.Unity.PrismApplication { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "2.0.0.0")] private void InitializeComponent() { global::Xamarin.Forms.Xaml.Extensions.LoadFromXaml(this, typeof(App)); } }
メソッドの実装
PrismApplicationを継承したことによって、現時点でApp.xaml.cs
のApp
クラスには赤い波線がついて、OnInitialized
とRegisterTypes
メソッドを実装するように求められています。
この2つのメソッドをAppクラスに追加します。同時にもともと定義されていたメソッド群は削除します(下のコードではコメントアウトしてありますが)。
public partial class App { //public App() //{ // InitializeComponent(); // MainPage = new MainPage(); //} //protected override void OnStart() //{ // // Handle when your app starts //} //protected override void OnSleep() //{ // // Handle when your app sleeps //} //protected override void OnResume() //{ // // Handle when your app resumes //} public App(IPlatformInitializer initializer=null):base(initializer) { } protected override void RegisterTypes(IContainerRegistry containerRegistry) { //ここではDIコンテナへの登録を記述する } protected override void OnInitialized() { InitializeComponent(); } }
コンストラクタの追加とネイティブ側からの呼び出し部分の変更
Appクラスのコンストラクタを以下のようにします.
引数でIPlatformInitializer
を受け取るようにします.
public App(IPlatformInitializer initializer=null):base(initializer) { }
IPlatformInitializer
については以下の記事が詳しいです.
プラットフォームごとのネイティブ側の機能をDIによって呼び分けるための仕組みです.OSごとに異なる処理をさせたい場合に使うようです.
コンストラクタのシグニチャの変更に伴い,コンストラクタを呼び出している各プラットフォーム側にも変更を加えます.
Android
MainActivity.csを開きます.
まずはAndroidInitializer
という名前でIPatformInitializer
を継承したクラスを作ります.これはMainActivityクラス内に追加します.
public class AndroidInitializer : IPlatformInitializer { public void RegisterTypes(IContainerRegistry containerRegistry) { //ここでコンテナに登録 //例↓ //var builder = new ContainerBuilder(); //builder.RegisterType<GreetingService>().As<IGreetingService>().SingleInstance(); //builder.Update(container); } }
次に,MainActivityクラスのOnCreate
メソッド内でAppクラスをインスタンス化している部分で,コンストラクタに上記のAndroidInitializer
クラスのインスタンスを渡すようにします.
protected override void OnCreate(Bundle savedInstanceState) { TabLayoutResource = Resource.Layout.Tabbar; ToolbarResource = Resource.Layout.Toolbar; base.OnCreate(savedInstanceState); Xamarin.Essentials.Platform.Init(this, savedInstanceState); global::Xamarin.Forms.Forms.Init(this, savedInstanceState); LoadApplication(new App(new AndroidInitializer()));// <- ここ }
iOS
AppDelegate.csファイルを開きます.
Android側と同じようにIPlatformInitializer
を継承したクラスを作ります.
public class iOSInitializer : IPlatformInitializer { public void RegisterTypes(IContainerRegistry containerRegistry) { } }
AppDelegateクラス内のFinishedLaunching
メソッドでAppクラスのコンストラクタを呼び出しているので,iOSInitializer
のインスタンスを渡すようにします.
public override bool FinishedLaunching(UIApplication app, NSDictionary options) { global::Xamarin.Forms.Forms.Init(); LoadApplication(new App(new iOSInitializer()));// <- ここ return base.FinishedLaunching(app, options); }
ここまでで,Prismの導入が完了です.
実装
Viewを作る
共有プロジェクト側にViews
フォルダを作成します.
その中に最初のViewとしてContentPageファイルを作成します.
名前はここではMainPage
とします.
ViewModelを作る
同じく共有プロジェクト側にViewModels
フォルダを作成します.
その中に先程のMainPageと対になるViewModelとしてClassファイルを作成します.
名前はここではMainPageViewModel
とします.
ViewとViewModelの名前の規則は大切で,PrismはView名に対して{View名}ViewModel
であることを前提としてViewとViewModelを結びつけます.
この規則はViewModelLocator
を介して変更可能ですが,今回はデフォルトの規則を使用します.
Viewをコンテナに登録する
Viewは全て事前にコンテナに登録しておく必要がある.
App.xaml.csファイル内のAppクラスのRegisterTypes
メソッドで登録を行う.
protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterForNavigation<MainPage>();//規則に従ってViewModelと結び付けられる containerRegistry.RegisterForNavigation<MainPage, MainPageViewModel>();//明示的に指定 }
RegisterForNavigation
メソッドに対してViewのクラスだけ渡して,ViewModelとの結びつけはPrismに任せるか,ViewとViewModelの双方を渡して明示的に結びつけることができます.
これでViewとViewModelの間でデータバインディングが可能になります.
起動時に表示するページを指定
OnInitializedメソッド内でNavigationService.NavigateAsync
メソッドでViewを指定します.
指定の仕方はいくつもあります.
protected override void OnInitialized() { InitializeComponent(); NavigationService.NavigateAsync(nameof(MainPage)); }
ViewModelへの変更
各ViewのViewModelではページ遷移などを担うNavigationServiceをコンストラクタの引数として受け取ります.これはPrismによってDIされます.
public class MainPageViewModel { INavigationService _navigationService; public MainPageViewModel(INavigationService navigationService) { _navigationService = navigationService; } }
例えばボタンがクリックされたときにページ遷移する場合は,_navigationService.GoBackAsync()
メソッドで遷移するのに使われます.
次に,ViewModelのクラスにBindableBase
クラスを継承させます.
このクラスを継承することによって,データバインディングをサポートする仕組みを使えるようになります.
private string _title="テスト2"; public string Title { get => _title; set => SetProperty(ref _title, value); }
また,ViewModelのクラスにINavigatedAware
を継承させます.
これを継承することで,ページ遷移する直前とページ遷移してきた直後に処理を行う以下の2つのメソッドが追加されます.
public void OnNavigatedFrom(INavigationParameters parameters) { } public void OnNavigatedTo(INavigationParameters parameters) { }