- 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 件のコメント:
コメントを投稿