PR: konifar/droidkaigi2016#108
アプリを起動(MainActivity)して任意のセッション詳細(SessionDetailActivity)を開いてSessionの状態を変更して戻る。このときMainActivityが破棄-再生成されていた場合にクラッシュする
MainActivity-SessionsFragment-ViewPager-SessionsTabFragmentの構造のなかで、それぞれのコンポーネント間で直接onActivityResult()を呼び出して伝搬する方法を取っていた。
本来はMainActivityが再生成される時SessionsFragmentも再生成されるのでMainActivity#onCreate()
の時にreplaceFragment()
などの初期化処理は不要になる。 しかしココで毎回Fragmentを作っているのでMainActivityが再生成される時、新たに作ったSessionsFragment(Aとする)とシステムが再生成したSessionsFragment(Bとする)が2つFragmentManager内に存在してしまう。
onCreateのあとMainActivity#onActivityResult()
が呼び出され、MainActivityはcurrentFragmentのonActivityResult()を呼び出す☆。この時currentFragmentにはAが入っている。AではonActivityResult()より前にonCreateView()が実行されてloadData()
も実行されているがloadData()
はSchedulers.io()で動作するのでonActivityResult()の時点では完了しておらず、そのためSessionsPagerAdapterも空となる。これによりクラッシュが発生する。
クラッシュ回避としてpositionとfragmentsのチェックをしている☆が、飽くまでクラッシュ回避だけであり根本的解決にはならない。
SessionsFragmentに以下のコードを足して画面遷移を繰り返すとFragmentManagerが保持するfragmentの数が増加していく事がわかる。再生成で復元されたFragmentと再度addするFragmentが溜まるため。
@Override
public void onPause() {
super.onPause();
Log.d("SessionsFragment", "fragments=" + getFragmentManager().getFragments().size());
}
直接onActivityResult()を呼び出して伝搬する方法をやめる必要がある。
最初の変更ではMainActivityでのFragment初期化を改善し、SessionDetailActivityの結果を受け取りたいSessionsTabFragment自身がstartActivityForResult()を呼び出す様にした。これにより各コンポーネントで直接onActivityResult()を伝搬させる構造を改善した。
次に問題となったのがSearchActivityから戻った時の挙動で、そもそもSearchActivityをstartActivity()で呼び出していたためこれは本質的には最初の変更とは関係がない。MainActivityの再生成が走った時は全部作りなおすからSessionsFragmentでもDBをreadしていて結果的にSearchActivityでの変更が反映されたように見えただけ。再生成が走らない場合はデータの反映は起こらない。
結構複雑なのでダイジェストで。
- SearchActivityをstartActivityForResult()で呼び出す構造にした。
- SearchedSessionsActivityでsavedInstanceStateを見てFragment初期化を制御しつつ直接onActivityResult()を伝搬しない構造にした。
- SearchActivityでSearchedSessionsActivityやSearchResultsAdapterでのSessionの変更を集めるようにした
- これを実現するためにSessionsFragment.OnChangeSessionListenerなどを追加した。
- MainActivityのSessionsFragmentでSearchActivityで結果を受け取り、変更があった場合は
loadData()
を呼び出してViewPager全体を作りなおすようにした。
SessionsTabFragmentがargsでListを受け取っているので、どうしてもデータ操作をなんらかの形で直接伝搬する必要がある。event busをなどで動的に変更を受け付ける構造にしたとしてもFragment再生成の時argsは元にもどるので変更は消失してしまう。
SessionsTabFragmentはデータ(List)を受け取るのではなくSQLの条件を受け取る形にし、OrmaAdapterなどを使って表示を行い、データベース操作による変化の検知、更新をOrmaAdapter側に委譲する設計がよいかもしれない(OrmaAdapterでできるか未検証だけど)。この構造ならSearchActivityから先の変更などをトラッキングする必要がなくなる。
本質的な話ではないけれど、Fragment内で getArguments().putXX() すれば再生成時の args を更新できます。