Androidアプリ開発 RxJava Pub/Subを実装する

2016年11月09日(編集2016年11月09日)
このエントリーをはてなブックマークに追加

Androidアプリ開発でRxJavaを使うと、Pub/Subを実装することができます。
この記事は、AndroidアプリでRxJavaを使ってPub/Subを実装する方法を記載した記事です。

環境はAndroid 7.1 (API level 25) です。

環境

  • OS X El Capitan
  • Oracle jdk version 1.8.0_72
  • Android Studio 2.2.2
  • android sdk 25

難易度

中級者向け

サンプルコード

Android-ReactiveX-Demo

Pub/Sub

Pub/Subは、他のクラスで行われているイベントを知りたいときに役立ちます。
デザインパターンのObserver patternと同じです。

ライブラリ

AndroidでPub/Subを実装する有名なライブラリには、

  • Otto
  • EventBus

があります。
どちらも枯れた使いやすいライブラリです。しかし、RxJavaを利用している環境の場合は、RxJavaで実装しましょう。アプリの容量は極力減らすべきです。

インストール

RxJavaのインストールは、build.gradleファイルに以下のように記載します。

{project_folder}/build.gradle
ext {
    rxandroidVersion = "1.2.1"
    rxjavaVersion = "1.1.6"
}

dependencies {
    compile 'io.reactivex:rxandroid:' + rootProject.rxandroidVersion
    // Because RxAndroid releases are few and far between, it is recommended you also
    // explicitly depend on RxJava's latest version for bug fixes and new features.
    compile 'io.reactivex:rxjava:' + rootProject.rxjavaVersion
}
        

Build → Rebuild Project

でRebuildすると、RxJavaが利用できます。

実装

サンプルとしてダイアログで入力した項目をアクティビティ画面に反映されるアプリを実装してみましょう。

まずは、アクティビティ画面を作成します。

{project_folder}/ui/PubSubActivity.java
public class PubSubActivity extends AppCompatActivity {

    private TextView result;
    private Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pub_sub);

        result = (TextView) findViewById(R.id.result);
        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(view -< {
            openDialog();
        });
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    private void openDialog() {
        PubSubInputDialogFragment newFragment = PubSubInputDialogFragment.newInstance();
        newFragment.show(getFragmentManager(), "dialog");
    }

    /**
     * set str data to result.
     *
     * @param str
     */
    public void setTextViewResult(String str) {
        result.setText(str);
    }
}
        

■ 実装の解説

1. DialogFragment

ダイアログ処理は以下のように記述します。

private void openDialog() {
    PubSubInputDialogFragment newFragment = PubSubInputDialogFragment.newInstance();
    newFragment.show(getFragmentManager(), "dialog");
}
        

入力ダイアログを、以下のように実装します。

{project_folder}/ui/PubSubInputDialogFragment.java
/**
 * for PubSub Sample
 */
public class PubSubInputDialogFragment extends DialogFragment {

    private TextInputLayout mWordLayout;
    private AutoCompleteTextView mWordView;

    public static PubSubInputDialogFragment newInstance() {
        PubSubInputDialogFragment fragment = new PubSubInputDialogFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_pub_sub_input_dialog, container, false);

        mWordLayout = (TextInputLayout) view.findViewById(R.id.word_text_input_layout);
        mWordView = (AutoCompleteTextView) view.findViewById(R.id.word);

        mWordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
                if (id == EditorInfo.IME_ACTION_SEND) {
                    String word = textView.getText().toString();
                    mWordLayout.setError(getInValidWordMessage(word));
                    return true;
                }
                return false;
            }
        });

        Button btn = (Button) view.findViewById(R.id.btn);
        btn.setOnClickListener(v -" {
            setResult();
        });

        return view;
    }

    /**
     * Return error message.
     * エラーがない場合は、nullを返す。
     *
     * @param str word
     * @return
     */
    public String getInValidWordMessage(String str) {
        if (TextUtils.isEmpty(str)) {
            return getString(R.string.validate_require, "word");
        } else if (str.length() < getResources().getInteger(R.integer.word_min_count)) {
            return getString(R.string.validate_minimun_word, "word", getResources().getInteger(R.integer.word_min_count));
        } else if (str.length() " getResources().getInteger(R.integer.word_max_count)) {
            return getString(R.string.validate_maximun_word, "word", getResources().getInteger(R.integer.word_max_count));
        } else {
            return null;
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }

    /**
     * 入力結果をActivityに設定する
     */
    private void setResult() {
        if (getActivity() instanceof PubSubActivity) {
            ((PubSubActivity) getActivity()).setTextViewResult(mWordView.getText().toString());
        }
        dismiss();
    }
}
        

■ 実装の解説

1. DialogFragment → Acitivtyへデータを設定

ダイアログのEditTextに入力した値をAcitivtyに反映させるメソッドを以下のように記述しています。

/**
 * 入力結果をActivityに設定する
 */
private void setResult() {
    if (getActivity() instanceof PubSubActivity) {
        ((PubSubActivity) getActivity()).setTextViewResult(mWordView.getText().toString());
    }
    dismiss();
}
        

呼び出し先のActivityをinstanceofで判定して対象Activityのsetメソッドを呼び出しています。他には、インターフェースを介してActivityと関連付ける方法もあります。後者の方がベストですが、コードがコールバックだらけになってしまいます。いずれにせよ、ダイアログとActivityが密に結びついてしまいます。

上記のコードをビルドしてアプリを立ち上げると、以下のような一連の処理が確認できます。

アプリの流れ

これをPub/Subで置き換えます。

RxBus

EventBusを実装します。RxJavaを利用しているのでRxBusの名称で作成します。

{project_folder}/eventbus/RxBus.java
public class RxBus {
    private final Subject<Object, Object" bus = new SerializedSubject<"(PublishSubject.create());

    public void send(@NonNull Object o) {
        bus.onNext(o);
    }

    public Observable<Object" toObserverable() {
        return bus;
    }

    public boolean hasObservers() {
        return bus.hasObservers();
    }
}
        

さらに、RxBusを生成するRxBusProviderを作成します。

{project_folder}/eventbus/RxBus.java
public class RxBusProvider {
    private static final RxBus BUS = new RxBus();

    private RxBusProvider() {
        // No instances.
    }

    public static RxBus getInstance() {
        return BUS;
    }
}
        

■ 実装の解説

1. RxBusの取得

シングルトンでRxBusを取得します。

public static RxBus getInstance() {
    return BUS;
}
        

RxBusオブジェクトをSingletonパターンで取得しています。Singletonパターンはデザインパターンの1つで、そのクラスのインスタンスが1つしか生成されないことを保証します。

ダイアログ側で実装します。

{project_folder}/ui/PubSubInputDialogFragment.java
/**
 * for PubSub Sample
 */
public class PubSubInputDialogFragment extends DialogFragment {

    private TextInputLayout mWordLayout;
    private AutoCompleteTextView mWordView;

    public static PubSubInputDialogFragment newInstance() {
        PubSubInputDialogFragment fragment = new PubSubInputDialogFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_pub_sub_input_dialog, container, false);

        mWordLayout = (TextInputLayout) view.findViewById(R.id.word_text_input_layout);
        mWordView = (AutoCompleteTextView) view.findViewById(R.id.word);

        mWordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
                if (id == EditorInfo.IME_ACTION_SEND) {
                    String word = textView.getText().toString();
                    mWordLayout.setError(getInValidWordMessage(word));
                    return true;
                }
                return false;
            }
        });

        Button btn = (Button) view.findViewById(R.id.btn);
        btn.setOnClickListener(v -" {
            setResult();
        });

        return view;
    }

    /**
     * Return error message.
     * エラーがない場合は、nullを返す。
     *
     * @param str word
     * @return
     */
    public String getInValidWordMessage(String str) {
        if (TextUtils.isEmpty(str)) {
            return getString(R.string.validate_require, "word");
        } else if (str.length() < getResources().getInteger(R.integer.word_min_count)) {
            return getString(R.string.validate_minimun_word, "word", getResources().getInteger(R.integer.word_min_count));
        } else if (str.length() " getResources().getInteger(R.integer.word_max_count)) {
            return getString(R.string.validate_maximun_word, "word", getResources().getInteger(R.integer.word_max_count));
        } else {
            return null;
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }

    /**
     * 入力結果をActivityに設定する
     */
    private void setResult() {
       +RxBusProvider.getInstance().send(new Result(mWordView.getText().toString()));
       -if (getActivity() instanceof PubSubActivity) {
            ((PubSubActivity) getActivity()).setTextViewResult(mWordView.getText().toString());
        }
        dismiss();
    }
}
        

■ 実装の解説

1. busへeventを送信する

busへeventを送信します。

RxBusProvider.getInstance().send(new Result(mWordView.getText().toString()));
        

sendメソッドは、RxJavaのSubjectクラスのonNext()メソッドを呼び出します。eventは、Activity側のSubscriberでlisteningします。

ResultはEditTextの結果を格納するために作成したシンプルなModelクラスです。

public class Result {
    private String result;

    public Result(String result) {
        this.result = result;
    }

    public String getResult() {
        return this.result;
    }
}
        

Activity側で取得するSubscriberを実装します。

{project_folder}/ui/PubSubActivity.java
public class PubSubActivity extends AppCompatActivity {

    private TextView result;
    private Button btn;

   +private CompositeSubscription compositeSubscription;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pub_sub);

        result = (TextView) findViewById(R.id.result);
        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(view -< {
            openDialog();
        });

        // pub sub
       +compositeSubscription = new CompositeSubscription();
        compositeSubscription.add(RxBusProvider.getInstance().
                toObserverable()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(o -< {
                    if (o instanceof Result) {
                        setTextViewResult(((Result) o).getResult());
                    }
                })
        );
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
       +if (compositeSubscription != null && !compositeSubscription.isUnsubscribed()) {
            compositeSubscription.unsubscribe();
        }
    }

    private void openDialog() {
        PubSubInputDialogFragment newFragment = PubSubInputDialogFragment.newInstance();
        newFragment.show(getFragmentManager(), "dialog");
    }

    /**
     * set str data to result.
     *
     * @param str
     */
    public void setTextViewResult(String str) {
        result.setText(str);
    }
}
        

■ 実装の解説

1. subscribeの実装

subscribeの実装は以下の部分です。

RxBusProvider.getInstance().
toObserverable()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(o -< {
    if (o instanceof Result) {
        setTextViewResult(((Result) o).getResult());
    }
}
        

observeOnは、それ以降のイベント処理(subscribeの関数の処理)を実行するスケジューラーを指定します。使ったことがないとわかりにくいと思いますが、AndroidSchedulers.mainThread()を利用するために必要な処理と理解しておきましょう。

2. CompositeSubscriptionの実装

CompositeSubscriptionは複数のSubscriptionをまとめてunsubscribeできます。

compositeSubscription.unsubscribe();
        

CompositeSubscriptionはunsubscribe()を呼び出すと、再利用できません。今回の場合、onDestroy()メソッドで破棄します。

ビルド

ビルドしてアプリを立ち上げます。

アプリの流れ

Pub/Subで値の受け渡しができました。

まとめ

Pub/Subを使うことで、コード量を大きく減らすことができます。RxJavaを使用しているなら利用しない理由はないので、実装してみてください。

関連サービス

Validation View Generator

関連記事

タグ検索で調べてみよう

Android7.0 RxJava Java8