nuits.jp blog

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

Prism for Xamarin.Forms入門 PageDialogService

お久しぶりです。
少し間が空いてしまいました。
本当はもう少しNavigationServiceを掘り下げたかったのですが、ちゃんと説明するにはもう少し時間がかかりそうなので、先にPageDialogServiceの説明をしたいと思います。
NavigationServiceについては、機会を見てまだ何回か書かせてもらおうと思います。

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

というわけで今回はPageDialogServiceについて説明させて頂こうと思います。

Overview

さて、PageDialogServiceとは、AlertDialogやConfirmDialogのようなインタラクティブなダイアログを提供するサービスです。
PageDialogServiceでは大きく以下の3種類のダイアログを提供してくれます。

  1. メッセージを通知するのみのアラートダイアログ
  2. 利用者に選択(Yes/No)を促す確認ダイアログ
  3. 利用者に複数の選択肢を提示するアクションシートダイアログ

Xamarin.Forms標準の仕組みとして元々これらのダイアログは提供されているのですが、標準の実装はPageのメソッドとして実装されており、そのままではViewModelからダイアログを表示するにはひと手間必要となります。
このため、Prismではこれらを直接利用するのではなく、ラッピングしてViewModelがViewへ直接依存しなくて済むような仕組みを設けています。

それでは具体的に、実装方法を見ていきたいと思います。

Startup

まずは空のPrism.Formsプロジェクトを作成してください。
今回のサンプルでは、PageDialogServiceという名称のプロジェクトを作成しました。
Prism.Formsプロジェクトの作成方法は、こちらをご覧ください。

www.nuits.jp

PageDialogServiceを利用する場合、上記のいずれのダイアログを利用する場合であっても、ViewModelに対してPrismからIPageDialogServiceをインジェクションしてもらって利用します。
まずはPCLプロジェクト(ここではPageDialogServiceプロジェクト)を開き、ViewModelフォルダーの下の、MainPageViewModelクラスを開いてください。

f:id:nuitsjp:20160904064933p:plain

ここに以下のように、IPageDialogServiceのprivateフィールドを追加し、Prismからコンストラクタ経由でインジェクションしてもらい、そのインスタンスをフィールドへ設定します。

private readonly IPageDialogService _pageDialogService;
public MainPageViewModel(IPageDialogService pageDialogService)
{
    _pageDialogService = pageDialogService;
}

IPageDialogServiceはインターフェースとして提供されていることから、ここでもテスタビリティが十分に考慮されることが見て取れます。
以上で準備は完了です。

AlertDialog

それではいよいよダイアログを表示してみましょう。
まずは何らかのメッセージをユーザに通知するためのAlertを表示してみます。

まずは、MainPageViewModelにダイアログを表示するコマンドを追加します。

public ICommand DisplayDialogCommand { get; }
public MainPageViewModel(IPageDialogService pageDialogService)
{
    _pageDialogService = pageDialogService;
    DisplayDialogCommand = new DelegateCommand(async () =>
    {
        await _pageDialogService.DisplayAlertAsync("Title", "Hello, Dialog.", "OK");
    });
}

ダイアログ表示にはDisplayAlertAsyncメソッドを利用します。
各引数は以下の通りです。

引数 説明
第一引数 ダイアログのタイトル
第二引数 ダイアログメッセージ
第三引数 ボタンのラベル

続いて、View側に作成したDisplayDialogCommandを呼び出すコードを追加しましょう。
MainPage.xamlを開いてください。

  <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
    <Label Text="{Binding Title}" />
    <Button Text="Display Dialog" Command="{Binding DisplayDialogCommand}"/>
  </StackLayout>

StackLayoutに新たにButtonを追加し、先ほど作成したDisplayDialogCommandを呼び出します。
実装は以上です。
それでは動かしてみましょう。
以下のがその表示結果です。

f:id:nuitsjp:20160904070510g:plain

左から、iPhone、Android、Windows 10 Mobileです。
簡単ですね!

ConfirmDialog

続いては、「Yes/No」を利用者に選択してもらう、確認ダイアログです。
確認ダイアログで入力を求めて、その入力結果を先ほどのAlertDialogに表示するようにします。
それではMainPageViewModel.csを開いて、先ほどのコードを以下のように改修しましょう。

public MainPageViewModel(IPageDialogService pageDialogService)
{
    _pageDialogService = pageDialogService;
    DisplayDialogCommand = new DelegateCommand(async () =>
    {
        var result = await _pageDialogService.DisplayAlertAsync("Title", "何れかを選んでください。", "はい", "いいえ");
        await _pageDialogService.DisplayAlertAsync("Title", $"選択結果:{result}", "OK");
    });
}

確認ダイアログも、先ほどと同じDisplayAlertAsyncを利用しますが、引数が一つ増えます。

引数 説明
第一引数 ダイアログのタイトル
第二引数 ダイアログメッセージ
第三引数 OKボタンのラベル
第四引数 キャンセルボタンのラベル

ユーザーの選択結果はboolで返却されます。
当然ではありますが、上記の場合は「はい」が押されればtrueが、「いいえ」が押されればfalseが返却されます。

以下のように動作します。

f:id:nuitsjp:20160904071357g:plain

ActionSheet

続いてはActionSheetです。
これは複数の選択肢をユーザに提供するための機能になります。
実際に見てみた方が早いでしょう。
想定としては、何らかのコンテンツをアプリ内からSNSへ共有するシーンを想定します。
また、その際にそのコンテンツを削除することもできるものとします。(ちょっと想定が苦しいですが。。。)

ではMainPageViewModel.csを開いて、新しくコマンドとその実体を追加しましょう。
以下のように、コードを修正してください。

public ICommand DisplayActionSheetCommand { get; }
public MainPageViewModel(IPageDialogService pageDialogService)
{
    ~中略
    DisplayActionSheetCommand = new DelegateCommand(async () =>
    {
        var result = await _pageDialogService.DisplayActionSheetAsync("共有先を選択してください。", "キャンセル", "削除", "Twitter", "LINE", "Facebook");
        await _pageDialogService.DisplayAlertAsync("Title", $"選択結果:{result}", "OK");
    });
}

ActionSheetを利用するには、DisplayActionSheetAsyncメソッドを利用します。
ActionSheetの利用方法は実のところ2種類ありますが、ここでは簡便な方を説明します。
もう一つの方法は、別途説明します。
さて、DisplayActionSheetAsyncメソッドの使い方です。

引数 説明
第一引数 ダイアログのタイトル
第二引数 キャンセルボタンのラベル
第三引数 削除(破壊?)を表すボタンのラベル
第四引数以降 それ以外の選択肢を表すボタンのラベル。複数指定可能

すべての引数は省略可能です。

続いて、上記のコマンドを実行するButtonをMainPage.xamlに追加します。

  <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
    <Label Text="{Binding Title}" />
    <Button Text="Display Dialog" Command="{Binding DisplayDialogCommand}"/>
    <Button Text="Display ActionSheet" Command="{Binding DisplayActionSheetCommand}"/>
  </StackLayout>

そして実際に動かした例が以下になります。

f:id:nuitsjp:20160904072040g:plain

キャンセルボタンと削除ボタンの表示が、各プラットフォームでやや異なりますね。

ちなみに、上記のコードの場合、選択結果がボタンのラベル文字列で返却されます。
返却された後文字列で条件分岐を記述する必要があり、ちょっと微妙だと思われる方も多いと思います。
そこでPrismでは対応するボタンにICommandを割り当てる仕組みが用意されています。

先ほどのダイアログと同等の見た目で、各ボタンに対して個別のコマンドを割り当てる例が以下のようになります。

public MainPageViewModel(IPageDialogService pageDialogService)
{
    ~中略
    DisplayActionSheetCommand = new DelegateCommand(async () =>
    {
        var cancelButton = 
            ActionSheetButton.CreateCancelButton(
                "キャンセル", 
                new DelegateCommand(
                    async () => await DisplayAlert("キャンセル")));
        var deleteButton = ActionSheetButton.CreateDestroyButton("削除", new DelegateCommand(async () => await DisplayAlert("削除")));
        var twitterButton = ActionSheetButton.CreateButton("Twitter", new DelegateCommand(async () => await DisplayAlert("Twitter")));
        var lineButton = ActionSheetButton.CreateButton("LINE", new DelegateCommand(async () => await DisplayAlert("LINE")));
        var facebookButton = ActionSheetButton.CreateButton("Facebook", new DelegateCommand(async () => await DisplayAlert("Facebook")));
        await _pageDialogService.DisplayActionSheetAsync("共有先を選択してください。", cancelButton, deleteButton, twitterButton, lineButton, facebookButton);
    });
}

private async Task DisplayAlert(string message)
{
    await _pageDialogService.DisplayAlertAsync("Title", $"選択結果:{message}", "OK");
}

ActionSheetButtonクラスを利用して個々のボタンを作成します。 第一引数にボタンのラベルを、第二引数にボタンを押されたときに実行するコマンドを指定します。
こうすることで、文字列による処理分岐を記載することなく、ユーザの選択内容によって処理を分岐することが可能になります。
ちょっとコードが助長になりますので、ケースによって先程の利用方法と取捨選択するのが良いかと思います。
ActionSheetを利用する場合、選択結果によって処理が大幅に異なることが多いと思いますので、基本的にはこちらのアプローチをお勧めしたいと思います。

なお、実行結果は先ほどと完全に同じになりますので、ここでは省略します。

最後に

今回はユーザにダイアログで何らかの通知を表示する、PageDialogServiceを紹介しました。
ViewModelからViewに一切触れずに実現されていることが、ご理解いただけたかと思います。
またIPageDialogServiceがインターフェースで提供されていることで、Mockを使ったテストも非常に容易になっており実にPrismらしいアプローチなのではないでしょうか?

といった辺りで、PageDialogServiceの回は終了となります。
次回はDependencyServiceをインジェクションする方法をご紹介したいと思います。

それではまた!