ユーザー一覧を取得して表示する
ユーザー一覧の表示は、API呼び出し処理の実装、RecyclerViewアダプターの定義、RecyclerViewHolderの定義を行い、API呼び出し完了時にアダプターへデータを設定するといった流れになります。
API呼び出し
前項で用意したrandomuser.meのAPIアクセサでユーザー一覧を取得します。API呼び出しのレスポンスはio.reactivex.Observableなので、Reactive Extensionsの作法に従ってレスポンスのハンドリングを実装できます。
特筆すべき点は、通信完了時に通知オブジェクト(mDataLoadedNotification)に対する通知だけを行い、Viewの更新処理などを記述しないため、簡潔に書けている点です。ここで言う通知オブジェクトは、RxJavaが提供するReactive ExtensionsにおけるSubjectですが、基本的にはObserverパターンにおけるSubjectという認識で問題ありません。
public class UserListFragment extends Fragment {
...
// ページ番号
private int mCurrentPageNumber;
// 通信の完了通知オブジェクト
private Subject<RandomUserResponse> mDataLoadedNotification = PublishSubject.create();
...
/**
* ユーザーリストをロードします。
*/
private void loadUserList() {
mCurrentPageNumber = mCurrentPageNumber + 1;
RandomUserApi.get().list(mCurrentPageNumber)
// 通信が失敗した場合は、空のレスポンスオブジェクトを利用
.onErrorReturnItem(new RandomUserResponse())
// 通信処理はバックグラウンドスレッドで行う
.subscribeOn(Schedulers.from(ChatApplication.getInstance().getThreadPoolExecutor()))
// レスポンスの処理をMAINスレッドで行う
.observeOn(AndroidSchedulers.mainThread())
// 通信が完了したら通知
.subscribe(mDataLoadedNotification::onNext);
}
...
}
そして、通信完了時の処理は、通信完了通知オブジェクトのリスナーとして実装します。
public class UserListFragment extends Fragment {
...
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
bindEvents();
}
...
/**
* イベントハンドラーを登録します。
*/
private void bindEvents() {
// 通信完了のリスナーを登録
mDataLoadedNotification.subscribe(this::onDataLoaded);
...
}
...
/**
* データのロード完了イベントをハンドリングします。
*
* @param data {@link RandomUserResponse}
*/
private void onDataLoaded(final RandomUserResponse data) {
// RecyclerViewのアダプタへデータを追加
mUserListAdapter.addItem(data.results);
}
}
RecyclerViewアダプターの定義
本項に関しては、インターネット上にさまざまな記事があるので、ソースコードを参照していただくことにして、ここでは言及されていないことが多い注意点のみを列挙し、詳細は省略します。
- 内部クラスとして定義する際はstaticクラスとする(暗黙的な参照がもたらすメモリリークを起こさないため)
- ActivityやFragmentの参照を持つ際には弱参照とする(ライフサイクルの異なるインスタンス間の強参照保持によるメモリリークを起こさないため)
- LayoutInflaterを初期化する時に、ContextThemeWrapperでテーマの適用を行う
RecyclerViewHolderの定義
ViewHolderの基本的な実装方法は他の記事に譲るとして、ここではRxJava+RxBindingライブラリとEventBusライブラリを利用する方法とそのメリットをご紹介します。

RecyclerViewの各行をクリックした際に何かの処理する場合、よく悩まされるのはダブルクリックの問題です。これをRxJava+RxBindingライブラリを用いることで簡単に解決できます。例えば下記コードのようにすると、1度イベントが発生したら500ミリ秒間はイベントが発生しないように制御できます。
public static class UserViewHolder extends RecyclerView.ViewHolder {
...
/**
* コンストラクタ
*/
private UserViewHolder(final View itemView) {
...
bindUiEvents();
}
/**
* UIイベントハンドラーを登録します。
*/
private void bindUiEvents() {
// クリックイベントをobservableとして扱う
RxView.clicks(itemView)
// 1度イベントが発生したら、500ミリ秒間は次のイベントを無視
.debounce(500, TimeUnit.MILLISECONDS)
// イベントのハンドリングはMAILスレッドで行う
.observeOn(AndroidSchedulers.mainThread())
// ハンドラーを設定
.subscribe(this::onClick);
}
...
}
RxBindingを使うためのbuild.gradle設定は以下のとおりで、ProGuardの設定は不要です。
dependencies {
...略...
compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
...略...
}
RecyclerViewの行クリックイベントをハンドリングする方法は、インターネット上でもいくつかの方法が提示されていますが、筆者がお勧めするのはEventBusを用いた方法です。EventBusを用いることで、イベントの発生源(今回であればViewHolder)とイベントハンドリングの実装(UserListFragment)を完全に分離できます。
public static class UserViewHolder extends RecyclerView.ViewHolder {
...
/**
* 行がクリックされた際に呼び出されます。
*/
private void onClick(final Object ignore) {
// EventBusへ通知
EventBus.getDefault().post(new Event.SelectUser(this));
}
}
public class UserListFragment extends Fragment {
...
/**
* {@link Event.SelectUser}イベントをハンドリングします。
*
* @param e {@link Event.SelectUser}
*/
@Subscribe
public void on(final Event.SelectUser e) {
final ChatDetailFragment fragment
= ChatDetailFragment.newInstance(e.viewHolder.mUser);
getFragmentManager()
.beginTransaction()
.addToBackStack(ChatDetailFragment.TAG)
.replace(R.id.container, fragment)
.commit();
}
}
EventBusを使うためのbuild.gradle設定とProGuardの設定は以下のとおりです。
dependencies {
...略...
compile 'org.greenrobot:eventbus:3.0.0'
...略...
}
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
