Prismを使ってXamarin.Formsアプリを作るときのメモ

#はじめに
モバイルに限らずアプリケーションの開発というと難しく感じられるかもしれませんが、シンプルなアプリケーションであれば「こうしたいときはこう書く」みたいな基本的なパターンを知っておくだけで作ることができます。

それはVisual Studioのような統合開発環境の進歩や様々なライブラリ、フレームワークの充実、言語自体の進歩のおかげだったりします。

しっかりしたアプリケーションを作るためには専門的な知識が沢山必要だったりするのでしょうが、せっかくこんなにアプリケーション開発のハードルが下がってきたのだから、要点だけ押さえてアプリケーションの開発を楽しんでみていいんじゃないかな、と思います。

この記事では「イチから」というわけにはいきませんが、Xamarin.FormsとPrismというアプリケーション開発のフレームワークを使った、「こうしたいときはこう書く」を紹介したいと思います。

#モバイルアプリの典型的な動作
モバイルアプリの動作は非常に大雑把に言って、「画面をタッチしたら画面が変わる」とか「画面をタッチしたら情報が更新される」とかです。

となると、イベント処理（ボタンを押す）、画面遷移（画面が変わる）、データバインディング（情報の更新）の基本的な書き方を知っていれば**シンプルな**アプリケーションが作れる、と思います。

そこで、この記事では以下の5つの項目について書きます。

- データバインディング
- イベント処理
- 画面遷移
- データの受け渡しを伴う画面遷移
- 状態によるボタンの有効無効

#前提

- Windows 10
- Visual Studio 2017
- Prism Template Pack インストール済み
- Prism Blank App（Xamarin.Forms）でプロジェクト作成済み


#１．データバインディング
バインディングしたいプロパティは`public`にします。
そして`setter`内で`SetProperty`メソッドを使います。
このメソッドがプロパティの値に変化があったときにView側に通知して表示が変化します。

```csharp:ViewModel(cs)
        private string _title;
        public string Title
        {
            get => _title;
            set => SetProperty(ref _title, value);
        }
```

```xml:View(xaml)
Title="{Binding Title}"
```

##プロパティが持つプロパティをバインディングしたい場合
View側で目的のプロパティまでのパスを「.」でつなげていきます。
以下の例は自作のGeolocaionクラスで、このクラスは`Location`プロパティを持っていて、その`Location`プロパティは`Latitude`プロパティを持っています。
`Geolocation`型のプロパティのプロパティである`Latitude`をバインディングしたい場合は、`Text="{Binding Path=Geolocation.Location.Latitude}"` とします。

Geolocation

```csharp:
        private Geolocation _geolocation;
        public Geolocation Geolocation
        {
            get => _geolocation;
            set => SetProperty(ref _geolocation, value);
        }
```

```xml
<Label Text="{Binding Path=Geolocation.Location.Latitude}" FontSize="15" HorizontalTextAlignment="End" HorizontalOptions="FillAndExpand"/>
```

##配列やListなどのCollectionをバインディングするとき
`ObservableCollection`を使います。
以下のように`ObservableCollection`型のプロパティを用意し、それをリスト形式の表示を行うUI要素（XamarinであればListView、WPFならListBoxなど）にバインディングします。

```csharp:ViewModel側
        public ObservableCollection<LocationInformation> Targets { get; set; } = new ObservableCollection<LocationInformation>();
```

```xml:View側
        <ListView AbsoluteLayout.LayoutFlags="All"
                  AbsoluteLayout.LayoutBounds="0,0,1,1"
                  HasUnevenRows="True"
                  ItemsSource="{Binding Path=Targets}">

            //以下略
```

この`ObservableCollection`型のプロパティに対して、`Add`や`RemoveAt`や`Clear`などのメソッドで変更を加えると、それがView側に反映されます。



#２．イベント処理
コードビハインドの`xaml.cs`ファイルにイベントハンドラを書くこともできますが、ここではViewModel側にイベントハンドラを書く方法を紹介します。

##Buttonが押されたとき

```xml
        <Button Text="text" 
                Command="{Binding Path=MyCommand}" />
```

```csharp
        public Command MyCommand =>
            new Command(() =>
            {
                //ボタンが押されたときの処理を書く
            });
```

##Buttonが押されたとき、文字列をパラメータとして渡す
`CommandParameter`に指定した文字列がViewModel側のCommandが呼び出されるときに引数として渡される。
以下の例ではView側で遷移先のViewを文字列で指定できるようにしている。

```xml
        <Button Text="text" 
                Command="{Binding Path=MyCommand}" 
                CommandParameter="SecondPageView"/>
```

```csharp
        public Command<string> MyCommand =>
            new Command<string>(name =>
            {
                _navigationService.NavigateAsync(name);
            });
```

##Button以外のUI要素がタップされた場合
Buttonの場合は押されたときに`Command`プロパティにバインディングしたCommandが実行されますが、Button以外のUI要素がタップされたときは`GestureRecgnaizers`プロパティを使います。

###Labelがタップされた場合

```xml
            <Label Text="{Binding Path=TargetInfo.Name}" FontSize="40" HorizontalOptions="FillAndExpand" HorizontalTextAlignment="Center">
                <Label.GestureRecognizers>
                    <TapGestureRecognizer Command="{Binding Path=NavigateCommand}" CommandParameter="SelectTargetPage"/>
                </Label.GestureRecognizers>
            </Label>
```

###ListViewでアイテムがタップされた場合
`EventToCommandBehavior`を使います。
そのUI要素の任意のイベントを拾いたい場合に使います。
`EventName`には拾いたいイベント名を指定します。
`Command`にはCommand型のプロパティを指定します。この例では選択されたアイテムのデータを引数として渡したいので、プロパティの定義では`Command<T>`クラスを使います。
`EventArgsParameterPath`プロパティにはListViewが持つプロパティのうち、Commandに渡したいプロパティの名前を指定します。この例では選択されたアイテムが格納される`SelectedItem`を指定します。
`EventName`も`EventArgsParameterPath`も、指定した文字列のイベントやプロパティをPrismが探して使用してくれます。

```xml
            <ListView.Behaviors>
                <behaviors:EventToCommandBehavior EventName="ItemSelected"
                                                  Command="{Binding Path=ItemSelectedCommand}"
                                                  EventArgsParameterPath="SelectedItem"/>
            </ListView.Behaviors>
```

```csharp
        public Command<LocationInformation> ItemSelectedCommand =>
            new Command<LocationInformation>(targetInfo =>
            {
                //アイテムが選択されたときの処理を書く。
                //引数targetInfo（名前は何でも良い）には選択されたアイテムのデータが入っている。
                //この例では`LocationInformation`型のコレクションをListViewにバインディングしているので、targetInfoは`LocationInformation`型のデータです。
            });
```



#３．画面遷移
上の例で出てきたとおり。
`NavigateAsync`メソッドで指定したViewに遷移できる。

```csharp
_navigationService.NavigateAsync("遷移先のView名");
```

じゃあこの`_navigationService`はどこからきたのかというと、そのViewModelのコンストラクタの引数として渡されてくるので、それをprivateなフィールドに入れてViewModel内で使えるようにしている。
`_navigationService`をViewModelのコンストラクタに渡すのはPrismがDIコンテナを使ってやってくれる。

```csharp
        private INavigationService _navigationService;
        public SelectTargetPageViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
        }
```

##データの受け渡しのある画面遷移
`NavigateAsync`には受け渡したいデータを指定できるOverrideがあるので、それを使います。
`NavigationParameters`はDictionaryと同じようにキーと値のペアで渡したいデータ（任意のオブジェクト）を格納していきます。
そして、`NavigateAsync`メソッドで遷移先のView名とともに`NavigationParameters`型のオブジェクトを渡します。
 
```csharp
                var parameters = new NavigationParameters {
                    { "キー名", targetInfo},
                };

                _navigationService.NavigateAsync(nameof(TargetDetailPage), parameters);
```

受け取る側のViewModelでは以下のようにしてデータを受け取ります。
`OnNavigatingTo`（または`OnNavigatedTo`）メソッドで受け取ります。
引数parametersに遷移先で`NavigateAsync`メソッドに渡した`NavigationParameters`オブジェクトが入ります。
それをキー名で取り出して`as`で型変換して利用します。

```csharp
        public void OnNavigatingTo(INavigationParameters parameters)
        {
            var targetInfo = parameters[nameof(LocationInformation)] as LocationInformation;

            if (targetInfo == null)
            {
                this.Title = "New";
                return;
            }

            this.TargetInfo = targetInfo;
            this.Title = "Edit";
        }
```



#４．画面遷移時の処理の流れ
画面遷移するときに遷移元と遷移先で後始末や準備、遷移元からのデータの受取などをしたい場合は、遷移元と遷移先でViewModelクラスに`INavigationAware`インタフェースを継承して遷移時に実行されるメソッドを実装する。

`INavigationAware`は以下の3つのメソッドの実装が強制されます。
そして、この順番に実行されます。

1. OnNavigatingTo（遷移先：描画前）
- OnNavigatedFrom（遷移元）
- OnNavigatedTo（遷移先：描画後）

データの受け渡しがある画面遷移の場合、遷移先の`OnNavigatingTo``OnNavigatedTo`でデータを受け取る。

他に、デバイスのホームボタンや戻るボタンが押されたときの動作は以下のとおりです。
##ルートページ（例えばMainPage）でデバイスの戻るボタンが押されたとき

1. OnSleep（ページ側）
- App.OnSleep

復帰時

1. MainPageコンストラクタ
- OnNavigatingTo
- OnNavigatedTo
- App.OnInitialized

##デバイスのホームボタンが押されたとき
1. OnSleep（ページ側）
- App.OnSleep

復帰時

1. OnResume（ページ側）
- App.OnResume

##アプリ起動時
1. MainPageコンストラクタ
- OnNavigatingTo
- OnNavigatedTo
- App.OnInitialized

##上スワイプでアプリ切替時（アプリ選択画面）
1. OnSleep（ページ側）
- App.OnSleep

復帰時

1. OnResume（ページ側）
- App.OnResume

##電源ボタンを押したとき
1. OnSleep（ページ側）
- App.OnSleep

復帰時

1. OnResume（ページ側）
- App.OnResume


##ナビゲーションバーの戻るボタン
1. OnNavigatedFrom
- OnNavigatedTo


##GobackAsyncメソッドで戻る場合
1. OnNavigatingTo
- OnNavigatedFrom
- OnNavigatedTo


##NavigateAsyncメソッドで遷移した場合
1. 遷移先コンストラクタ
- OnNavigatingTo
- OnNavigatedFrom
- OnNavigatedTo



#５．状態によるボタンの有効、無効
`DelegateCommand`を使います。

```csharp
        public ICommand MyCommand =>
            new DelegateCommand(() =>
            {
                //実行したい処理
            }, () =>
            {
                //Buttonの有効、無効を判定したい条件
                //例えば、何かしらかのUI要素にバインディングしている値が特定の値かどうか
            }).
            ObservesProperty(() => /*有効無効の判定に使いたいプロパティ*/);
```

例えば、Entry（WPFでいうTextBox）コントロールに`Name`プロパティをバインディングしていて、そのEntryに入力がなければButtonは無効、入力があれば有効、というような動作をさせたい場合は以下のようにする。

```csharp
        public ICommand MyCommand =>
            new DelegateCommand(() =>
            {
                //実行したい処理
            }, () =>
            {
                //Buttonの有効、無効を判定したい条件
                return string.IsNullOrWhiteSpace(this.Name);
            }).
            ObservesProperty(() => this.Name);
```

複数のプロパティを条件としたい場合、例えば2つのEntryコントロールの両方に入力がある場合だけButtonを有効にする場合は以下のようにする。

```csharp
        public ICommand MyCommand =>
            new DelegateCommand(() =>
            {
                //実行したい処理
            }, () =>
            {
                //Buttonの有効、無効を判定したい条件
                return string.IsNullOrWhiteSpace(this.Name) && string.IsNullOrWhiteSpace(this.Age);
            })
            .ObservesProperty(() => this.Name)
            .ObservesProperty(() => this.Age);
```


#おわりに
ざっくり大雑把にXamarin.FormsとPrismを使ってモバイルアプリケーションを開発するときの基本的な「こうしたいときはこう書く」を紹介してみました。

たくさんあるようなないような内容でしたが、個人的にはこれだけ知っていればそれなりのアプリケーションを作れると思います。

説明をもっとわかりやすく書けたらいいなとおもいつつ、今回のところはこのへんにしておきたいと思います。