読者です 読者をやめる 読者になる 読者になる

ぴよぴよエンジニアの日記

クラウドベンダーに勤める見習いSEの日記です。発言は私自身の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

INavigationService.NavigateAsync で遷移先ページの ViewModel が生成されるタイミングについて

昨日開催されました @nuits_jpさん による「JXUGC #18 フォローアップハンズオン Prism & Moqをさわってみよう!」に参加してきました.

jxug.connpass.com


ハンズオンでしょーもないことにハマってしまったのですが、それについて懇親会でにゅいさんに教えていただきました.

今回は Prism.Forms の INavigationService.NavigateAsync で、遷移先ページの ViewModel が生成されるタイミングについてです.



ViewModel が生成されるタイミング

namespace PrismSample.ViewModels
{
    public class MainPageViewModel : BindableBase, IConfirmNavigationAsync
    {
        private readonly IPageDialogService _pageDialogService;
        private readonly INavigationService _navigationService;
        public ICommand NavigationCommand => new DelegateCommand(Navigate);

        public MainPageViewModel(INavigationService navigationService, IPageDialogService pageDialogService)
        {
            _navigationService = navigationService;
            _pageDialogService = pageDialogService;
        }

        public void Navigate()
        {
            _navigationService.NavigateAsync("SecondPage");
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {
            
        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
            
        }

        public async Task<bool> CanNavigateAsync(NavigationParameters parameters)
        {
            return await _pageDialogService.DisplayAlertAsync("Navigation", "Navigate to Second ?", "Yes", "No");
        }
    }
}

遷移元ページがこのように、ページ遷移する際に確認ダイアログを表示する機能を持ちます.

普通だったら、CanNavigateAsync が呼び出されたあとに遷移先ページと遷移先ページの ViewModel が生成されそうな気がしますが、実は NavigateAsync の段階ですでに ViewModel のコンストラクタが呼び出されます.


そして今回、私がハマってしまったのは、遷移先ページの ViewModel でクラッシュするコードを書いてしまったことです.

namespace PrismSample.ViewModels
{
    public class SecondPageViewModel : BindableBase
    {
        public SecondPageViewModel()
        {
            Debug.WriteLine("SecondPageViewModel called ...");
            string s = null;
            var sub = s.Substring(0, 10);
        }
    }
}

例えば、このように ViewModel のコンストラクタでクラッシュするコードを書きます.

このとき起こる挙動としては、ボタンを押してもダイアログが表示されず画面遷移も行われません.
NavigateAsync に await を付けない限り例外は握りつぶされてしまいます.
あたかも遷移元に不備があるような挙動になるわけです.


これまでのことをまとめます.

  • 遷移先ページの ViewModel が生成されるのは CanNavigateAsync の前で、NavigateAsync の段階ですでに生成されている
  • 遷移先ページの ViewModel の例外は await を付けない限りキャッチされない


ダイアログの可否に関わらず ViewModel が生成されるわけなので、ViewModel のコンストラクタでは ReactiveProperty の初期化などにとどめるのが良いでしょう(にゅいさん談).

また、NavigateAsync まわりで意図しない挙動が発生した場合、一旦 await を付けるとエラーの内容が鮮明になるかもしれません.



多階層の画面遷移ではどうなるか

public void Navigate()
{
    _navigationService.NavigateAsync("SecondPage/ThirdPage/ForthPage");
}

先程の例で多階層の画面遷移を行う場合の ViewModel 生成のタイミングはどうなるでしょうか.

結果は、NavigateAsync のタイミングで SecondPageViewModel, ThirdPageViewModel, ForthPageViewModel の順番でコンストラクタが呼び出されます.

多階層にしても結果は同じでした。




以上です.