fresh digitable

めんどくさかったなってことを振り返ったり振り返らなかったりするための記録

NavController.currentBackstackEntryをみながら戻りたい画面までpopBackStack()する

前提とやりたいこと

関係しそうな環境:

"androidx.navigation:navigation-fragment-ktx:2.3.0"
"androidx.navigation:navigation-ui-ktx:2.3.0"

ツイッタークライアントを作っている。起動したらホームタイムラインを表示して、そこからツイートの詳細やユーザーのプロフィールのFragmentに遷移したり、会話を読んだりできる。また、自分で管理しているユーザーリストを表示して、そのユーザーリストのタイムラインを見ることもできる。他にもいくつか機能はあるけど、今回は割愛。

基本的な画面遷移の構成はいわゆるmaster/detail flowとかいうやつで、このアプリでは次の3つの遷移ができるようになっている。

  • リスト(master)→詳細(detail)
  • 詳細(detail)→リスト(master) 例:会話とかタグで検索(未実装だけどこれから作る)とか
  • リスト(master)→リスト(master) 例:会話とかユーザーリスト(ナビゲーションドロワーから飛べる)とか

リストのFragmentにはどのエンドポイントを叩くかを決めるパラメータをArgumentとして渡している。論理的には無限に画面遷移できるので、たくさん画面遷移した後でも簡単にホームタイムラインに戻ってこられるように、ナビゲーションドロワーでホームタイムラインへ遷移する機能を作りたい。

困ったことと解決策

普通にandroidx.navigationのNavController.popBackstack(Int, Boolean)を呼ぶと最初に見つかったFragmentまでしか戻れない。

setGraph()をやる時にバックスタックの状態をクリアするかな?と考えて内部の処理を読んでみたら、今まで持ってたグラフのIDとinclusive: trueを渡していたので、inclusive: falseに変えてやってみた。すると画面自体は巻き戻るものの、バックスタックの状態が変わった時に呼ばれるコールバックが呼ばれず、画面の状態に追従できなくなっていた。

実際のところ、これはナビゲーショングラフのルートまで戻る処理であって、ホームタイムラインのFragmentNavControllerのバックスタックからポップされている。ここでようやく、NavControllerのバックスタックというのがFragmentのバックスタックとは違うのだという事に気づいた。

NavControllercurrentBackstackEntryというプロパティを持っていて、これがNavControllerが管理しているバックスタックである。私はこれをFragmentが管理しているバックスタックエンティティ(か、あるいはそれに追従するもの)だと思っていたのだが実際のところはそこまで関連がない。

currentBackstackEntryFragmentのIDや渡したArgumentを持っている。また、popBackStack()を呼んだらすぐに変わるので、ホームタイムラインを表示するためのクエリパラメータを持ったBackstackEntryが出てくるまでpopBackStack()を呼び続ければよい。

github.com