SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

BizReach Tech Blog

Android 8.0とAndroid Studio 3.0で始めるReactive Extensionsを用いたアプリ開発

BizReach Tech Blog 第2回


ユーザー一覧を取得して表示する

 ユーザー一覧の表示は、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)を完全に分離できます。

ViewHolderでイベントを通知
public static class UserViewHolder extends RecyclerView.ViewHolder {
    ...
    
    /**
     * 行がクリックされた際に呼び出されます。
     */
    private void onClick(final Object ignore) {
        // EventBusへ通知
        EventBus.getDefault().post(new Event.SelectUser(this));
    }
}
Fragmentでイベントをハンドリング
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 { *; }

次のページ
データ更新処理

この記事は参考になりましたか?

BizReach Tech Blog連載記事一覧

もっと読む

この記事の著者

岩井 友希(株式会社ビズリーチ)(イワイ トモキ)

SIer、フリーランスを経て、2015年にビズリーチへ入社。 Java、Python、Android、iOS、Linuxたまにセキュリティとかのキーワードに反応するエンジニアです。 github:https://github.com/TomokiIwai 株式会社ビズリーチについて:https://www.bizreach.co.jp/recruit/

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/10676 2018/10/17 15:47

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー