nuits.jp blog

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

Prism for Xamarin.Forms Navigationイベント詳細 NavigationPage編

どうも、いまだかつてXamarinでアプリを一本も完成させたことのない初心者です。

本エントリーは[学生さん・初心者さん大歓迎!]Xamarin Advent Calendar 2016の12日目のエントリーとして投稿しました。

前日はbetatさんグッとくる Xamarin Studio のショートカット・小技集
翌日はaki_lua87さん[Xamarin.Forms]各プラットフォーム固有機能からFormsで作成したViewを表示するです!

という訳で、今回はPrism for Xamarin.FormsでNavigationPageを利用して画面遷移を実装した場合の、詳細な情報を調査してみました。
正確な情報を整理するのに、結構苦労させられた力作ですので、しばらくお付き合いください。

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

Prism for Xamarin.Forms入門 目次 - nuits.jp blog

前提条件

以下の環境にて調査しています。特にPrismはpre版であり、今後変更が想定されますのでご注意ください。
(というか、あってほしい点がいくつかあります) 変更があった場合には、また新しいエントリーとして投稿しますので、その際はまたご覧いただけるとうれしいです。

  • Xamarin.Forms 2.3.3.175
  • Prism for Xamarin.Forms 6.3-pre1
  • Android Emulator(VSとAndroid SDK双方) API23
  • iOS 10.2
  • UWP 10.0.14393.0

最初に(ここだけでも読んでほしい!)

調査していて非常に重要な点にいくつか気が付きました。 詳細に目を通さない方もここだけは読んでください。
ポイントは以下の3点です。

  1. Appearing/Disappearingを利用したコードは可能な限り控える事
  2. 画面を戻る際は、現時点ではCanNavigate(Async)/OnNavigatingToの利用は要注意
  3. 画面遷移のタイミングとOnNavigatedFrom/OnNavigatedToの呼び出し順がプラットフォームによって異なる点に注意

Appearing/Disappearingを利用したコードは可能な限り控える事

プラットフォームによって動作が違い過ぎて非常にやばいです。
詳細はリンク先の調査結果明細を見ていただいても良いのですが、実行順やそもそも実行の有無までプラットフォームによって異なります。
UWPでもPhoneとDesktopで異なります。

Microsoft OneDrive - Access files anywhere. Create docs with free Office Online.

特に、Androidではアプリケーションがバックグラウンドに行く際に、ナビゲーションスタック上の全画面のDisappearingが呼び出され、フォアグラウンドに戻る際に全画面のAppearingが呼ばれるのが、かな~りデンジャラスです。
これは、カメラの撮影やメディアのピッカーなんかでも引き起こされます。

画面の初期化処理に利用していると、致命的なバグを引き起こしかねないため注意が必要でしょう。
Prismの提供するイベントハンドラを利用して、これらを利用しない方が設計や実装が容易になると思われます。

画面を戻る際は、現時点ではCanNavigate(Async)/OnNavigatingToの利用は要注意

現時点ではAndroidやWin10mの物理バックキーや、NavigationPageの左上の戻るボタンで以下のハンドラが呼び出されません。

  • CanNavigateAsync
  • CanNavigate
  • OnNavigatingTo

PrismのIssueに上がっていないように思えますが、あまり良い事ではないと思いますので、ちゃんと調べた上でIssue登録するかもしれません。(プルリクチャンス!)
この為、戻る際に上のイベントを期待するのは現時点では危険だと思います。

画面遷移のタイミングとOnNavigatedFrom/OnNavigatedToの呼び出し順がプラットフォームによって異なる点に注意

これは前述の2点に比べると、まだ些細な事です。
OnNavigatedFrom/OnNavigatedToの呼び出しはUWPの場合、画面遷移前に呼び出されますが、iOSやAndroidでは画面遷移後に呼び出されます。
この為、画面の初期化をOnNavigatedToで実装していると、iOSやAndroidでは画面が一度表示されてから更新される形になるため、一工夫必要かもしれませんね。

イベント一覧

さて、Xamari.FormsおよびPrism for Xamarin.Formsでは画面遷移する際に以下のイベントが定義されています。
「基本的に」発生する順に並べています。

ViewとViewModel双方で利用可能なイベントがありますが、どの場合であってもView > ViewModelの順番で呼び出されます。
INavigationAwareなどが実はViewでも実装可能であることは、案外知られていないかもしれませんね。

発生順序 Event View ViewModel
1 コンストラクタ
2 IConfirmNavigationAsync#CanNavigateAsync -
3 IConfirmNavigation#CanNavigate -
4 INavigationAware#OnNavigatingTo
5 INavigationAware#OnNavigatedFrom
6 INavigationAware#OnNavigatedTo
7 IDestructible#Destroy
? Page#Appearing -
? Page#Disappearing -

コンストラクタ

インスタンスを生成します。。。という事が言いたいわけではないですw
DeepLinkを利用する場合、ちょっと注意が必要です。
Prismでは、DeepLinkを利用する場合、最初に遷移先の全画面のViewとViewModelのインスタンスを生成します。
その後、個々の遷移イベントが呼び出されます。

DeepLinkなんて利用しないし、と思われる方もいるかもしれませんがNavigationPageからTopPageへの指定はDeepLinkですし、TabbedPageなどでも関係します。

この為、コンストラクタに複雑な処理を含まることは避けるべきでしょう。
ReactivePropertyなどのフィールドの初期化程度に収めるのが無難です。

IConfirmNavigationAsync#CanNavigateAsync

画面遷移前に遷移するかどうか?例えば確認ダイアログなどを利用して判定することができます。
詳細はこちらをご覧ください。

Prism for Xamarin.Forms入門 IConfirmNavigationAsync - nuits.jp blog

このインターフェースはViewModelでのみ実装可能です。

IConfirmNavigation#CanNavigate

こちらは同期処理版です。
非同期処理版に比べてちょっと使い勝手が良くありません。
またIConfirmNavigationAsyncが実装されていた場合、そちらが優先的に実行され、こちらは呼び出されませんので注意してください。
非同期版と同様に、このインターフェースはViewModelでのみ実装可能です。

INavigationAware#OnNavigatingTo

画面遷移の前処理を実装します。
画面Aから画面Bに遷移する場合に、画面Bに実装されたOnNavigatingToが呼び出されます。
次の画面に表示するデータの取得や初期表示処理を実装します。

現時点では、物理バックキーやNavigationPageの上部の戻るボタンから戻った場合に、処理が実行されないため注意が必要です。

INavigationAware#OnNavigatedFrom

画面Aから画面Bに遷移する場合に、画面Aに実装されたOnNavigatedFromが呼び出されます。
画面の退場処理を実装します。
大きなデータを抱え込んだ画面などでリソースの解放に利用するのが良いでしょう。
なお、Prism 6.2では物理バックキーやNavigationPageの上部の戻るボタンから戻る場合に呼び出されない問題があるので注意が必要です。

とはいえ、ガベージコレクトされるので致命的でもありません。
Prism 6.2の場合、一部のリソース(ReactivePropertyの解放など)は、デストラクタと組み合わせて開放する必要があるでしょう。

INavigationAware#OnNavigatedTo

画面Aから画面Bに遷移する場合に、画面Bに実装されたOnNavigatedToが呼び出されます。
画面の初期化処理を実装します。
UWPでは画面の物理的な遷移前に呼ばれ、AndroidやiOSでは物理的な遷移後に呼ばれるため注意が必要です。
なおこちらも、Prism 6.2では物理バックキーやNavigationPageの上部の戻るボタンから戻る場合に呼び出されない問題があるので注意が必要です。

とはいえ、ViewModelのインスタンスは戻る際には再利用されるため、致命的という訳でもありません。

IDestructible#Destroy

このイベントは画面遷移を戻る際にのみ呼び出されます。
リソースの解放処理に利用するのが良いでしょう。
特にReactivePropertyを利用している場合、Modelを監視しているReactivePropertyは明示的に開放してあげないとメモリーリークを引き起こすため、このイベントハンドラで解放してあげるよが良いでしょう。
ネイティブリソースを保持している場合なども、ここで明示的に開放しましょう。

ただし1点注意があります。
NavigationServiceの絶対パス遷移をした場合、Prism 6.3-pre1ではスタック上のViewやViewModelのDestroyが呼び出されません。
これは開発サイドでも把握されており、6.3の正式版までには対応される予定です。

github.com

「help wanted」タグが付いているのでひそかにContributeを狙っていて、いま調査中です。Brian氏のバケーション中に調査したい!w

Page#Appearing

公式のAPIドキュメントによると

Indicates that the Page is about to appear.

ページを表示する際に呼ばれるとありますが、実際には呼ばれるタイミングや、条件がまちまちです。
デンジャラスです。
注意して利用しましょう。
詳細は、こちらのリンクを見ていただいても良いかもしれませんが、みたらかえって分かりにくいかもしれません(´・ω・`)

Microsoft OneDrive - Access files anywhere. Create docs with free Office Online.

あくまで自分の調査用なので。。。

Page#Disappearing

公式のAPIドキュメントによると

Indicates that the Page is about to cease displaying.

ページを表示が終了する際に呼ばれるとありますが、Appearingと同様に、実際には呼ばれるタイミングや、条件がまちまちです。
注意して利用しましょう。
同じく詳細はこちらを参考にしていただいても。。。う~んw

Microsoft OneDrive - Access files anywhere. Create docs with free Office Online.

最後に

さて現時点で、設計・実装上はまりにくいようにするには、次のように実装するのが良いでしょう。

  1. 基本はPrismのインターフェースベースのイベントハンドリングを利用する
  2. イベントハンドリングは基本的にViewModelに統一する
  3. 画面遷移時の前画面の終了処理をINavigationAware#OnNavigatedFromに実装する
  4. 画面遷移時の初期表示処理をINavigationAware#OnNavigatedToに実装する
  5. IConfirmNavigationAsync#CanNavigateAsync、INavigationAware#OnNavigatingToを利用する場合は戻る処理に要注意
  6. Appearing/Disappearingを使うのはやめとけ

という訳で、今回はここまでです。
次はTabbedPageについてまとめたいと思います。

それではまた!