Skip to content

Instantly share code, notes, and snippets.

@sys1yagi
Last active February 28, 2016 11:56
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sys1yagi/b0596187917501331707 to your computer and use it in GitHub Desktop.
Save sys1yagi/b0596187917501331707 to your computer and use it in GitHub Desktop.

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から先の変更などをトラッキングする必要がなくなる。

@yanzm
Copy link

yanzm commented Feb 28, 2016

本質的な話ではないけれど、Fragment内で getArguments().putXX() すれば再生成時の args を更新できます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment