2016年10月21日金曜日

BehaviorSubject を使って Activity と Fragment のデータの読み込みを待ち合わせる

画面構成は
  • MainActivity
    • TabLayout + ViewPager
    • ViewPagerの各ページは MainFragment
  • MainFragment
    • RecyclerView

やりたいことは
  • MainActivity
    • 各ページで共通のデータ(以後 CommonData)をサーバーから取得する
  • MainFragment
    • ページ特有のデータ(以後 SpecificData)をサーバーから取得する
    • MainActivity から CommonData をもらう
    • CommonData と SpecificData 両方の読み込みが終わったら RecyclerView にデータを追加する

キモになるのが、CommonData と SpecificData 両方の読み込みが終わるのを待ち合わせたいというところです。
CommonData の読み込みが終わる前に生成された MainFragment なら両方の読み込みを待ち合わせるし、 CommonData の読み込みが終わった後に生成された MainFragment なら SpecificData の読み込みだけ待てばいいわけです。
でも、この2つの状態をわけて処理すると煩雑になってしまいます。

そこで、BehaviorSubject を使って、CommonData の値を MainFragment 側に渡せるようにします。 BehaviorSubject は onNext() が呼ばれたときにその値を通知し、さらにその値をキャッシュします。新しく subscribe されると、最新のキャッシュした値があればその時点で通知します。

public class MainActivity extends AppCompatActivity implements MainFragment.MainFragmentListener { private final BehaviorSubject<CommonData> commonDataBehaviorSubject = BehaviorSubject.create(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... loadCommonData(); } private void loadCommonData() { subscription = DataRetriever.getInstance().getCommonData() .onErrorReturn(new Func1<Throwable, CommonData>() { @Override public CommonData call(Throwable throwable) { // エラーのときはデータがないものとして扱う return CommonData.empty(); } }) .subscribe(new Action1<CommonData>() { @Override public void call(CommonData commonData) { commonDataBehaviorSubject.onNext(commonData); } }); } @NonNull @Override public Observable<CommonData> getCommonDataObservable() { return commonDataBehaviorSubject; } } public class MainFragment extends Fragment { public interface MainFragmentListener { @NonNull Observable<CommonData> getCommonDataObservable(); } @Nullable private MainFragmentListener listener; ... @Override public void onAttach(Context context) { super.onAttach(context); if (context instanceof MainFragmentListener) { listener = (MainFragmentListener) context; } } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (adapter == null) { adapter = new DataAdapter(); load(); } recyclerView.setAdapter(adapter); } void load() { recyclerView.setVisibility(View.GONE); progressView.setVisibility(View.VISIBLE); // 共通データとタブ独自のデータ両方揃うまで待ち合わせ subscription = Observable .combineLatest( getCommonDataObservable(), getSpecificDataObservable(), new Func2<CommonData, SpecificData, Pair<CommonData, SpecificData>>() { @Override public Pair<CommonData, SpecificData> call(CommonData commonData, SpecificData specificData) { return new Pair<>(commonData, specificData); } }) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Pair<CommonData, SpecificData>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { recyclerView.setVisibility(View.VISIBLE); progressView.setVisibility(View.GONE); } @Override public void onNext(Pair<CommonData, SpecificData> combinedData) { recyclerView.setVisibility(View.VISIBLE); progressView.setVisibility(View.GONE); final List<String> list = new ArrayList<>(); final CommonData commonData = combinedData.first; list.add("CommonData : " + (commonData.isEmpty() ? "empty" : commonData.getData())); final SpecificData specificData = combinedData.second; list.addAll(specificData.getData()); adapter.addAll(list); } }); } /** * 共通のデータを取得 */ private Observable<CommonData> getCommonDataObservable() { return listener != null // first() を介して onComplete()が呼ばれるようにしている ? listener.getCommonDataObservable().first() : Observable.just(CommonData.empty()); } /** * このタブ独自のデータを取得 */ private Observable<SpecificData> getSpecificDataObservable() { final int position = getArguments() == null ? -1 : getArguments().getInt(ARGS_POSITION); return DataRetriever.getInstance().getSpecificData(position); } ... }

さらに、MainActivity に SwipeRefreshLayout を追加して、PullToRefresh で共通データを取り直し、各 MainFragment にもデータを取り直させる処理を追加したサンプルが
https://github.com/yanzm/BehaviorSubjectSample
です。


0 件のコメント:

コメントを投稿