LoginSignup
73
66

More than 5 years have passed since last update.

RxJavaでonActivityResultの煩わしさを解消する

Last updated at Posted at 2015-10-29

RxJavaをAndroidの開発に取り込みたく勉強しており、その成果第一弾として Activity#onActivityResult(int, int, Bundle) の煩わしさを解消するライブラリを作ってみました。

アクティビティの開始と結果を受け取るところが分かれてしまうため、行ったりきたりしなけれいけないので正直めんどくさいと感じてました('A`)

そんなおり、RxJavaを勉強し始め、RxPermissions というAndroid6.0 より導入された新しいパーミッションモデルのためのライブラリと出会いました。
RxJavaの勉強を兼ねてソースコードリーディング(クラスが3つだけなので非常に読みやすい)していましたが、同じような仕組みで onActivityResult も楽に書けるじゃねということで実際に作ってみました。

ライブラリはGitHubにありますのでよければ使ってみてください。

Activity#onActivityResultの面倒くさいところ

この onActivityResult が出てくる場面といえば、他のアクティビティを起動して、その結果をうけとる時ですね。例えば次のようなコードになるかと思います。


public class MainActivity extends Activity {

    private static final int REQUEST = 1;

    // ② 結果を受け取る
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == REQUEST && resultCode == RESULT_OK) {
            onReceiveResult(data);
        }
    }

    // ① 別のアクティビティを起動
    private void startOtherActivity() {
        Intent launchIntent = new Intent(this, OtherActivity.class);
        startActivityForResult(launchIntent, REQUEST);
    }

    // ③ 結果を受け取って何かする
    private void onReceiveResult(Intent data) {
    }
}

アクティの開始と結果の受け取りの処理が散在してしまい、見通しが悪いです。そんな時、RxJavaを使うことで解決することができます。

RxJavaの導入

※ RxJavaとは、という部分な割愛させていただきます。

RxJavaを導入することで、アクティの開始~結果の受け取りの時間の流れに沿った形で処理を書けるようになります。上記の例を書きなおしてみると、

// ※ 処理のイメージです
private void startOtherActivityUsingRxJava() {
    // ① 別のアクティビティを起動
    Intent launchIntent = new Intent(this, OtherActivity.class);
    startActivityForResult(launchIntent, REQUEST)
        // ② 結果を受け取る
        .filter(result -> result.requestCode == RESULT_OK)
        .subscribe(result -> {
            final Intent data = result.data;
            // ③ 何かする
        })
}

OtherActivity を起動して、結果がOKだったらデータから値を取り出して何かするという処理が、RxJavaを使うことで非常にすっきり書くことができます。トップダウンで読むことができるようになるのがRxJavaもといリアクティブプログラミングのいいところではないでしょうか。

ライブラリの使い方

これを踏まえ作成したライブラリは、上記の機能を少し拡張したものになります。

他のアクティビティを開始、結果を受け取ることができるのはアクティビティ以外にフラグメントも可能です。ライブラリはアクティビティの起動元にどれがきても大丈夫なようにしています。

public class MainActivity extends Activity {
    // ライブラリのメインとなるクラスで、アクティビティの開始、Observableの生成を行う
    private RxActivityLauncher mLauncher;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mLauncher = RxActivityLauncher.from(this);
    }

    // 使い方は同じです
    private void startOtherActivityUsingRxJava() {
        Intent launchIntent = new Intent(this, OtherActivity.class);
        mLauncher.startActivityForResult(launchIntent, REQUEST)
                 .filter(result -> result.requestCode == RESULT_OK)
                 .subscribe(result -> {
                     final Intent data = result.data;
                     // 何かする
                 })
    }   
}

// or

public class MainFragment extends Fragment {
    private RxActivityLauncher mLauncher;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // これもOK
        mLauncher = RxActivityLauncher.from(this);
    }
}

また暗黙インテントを使用する場合、ActivityNotFoundExceptionSecurityException が発生する可能性があります。そんな時も、

private void startActivityThenOccurException() {
    Intent intent = new Intent(NOT_EXIST_ACTION);
    mLauncher.startActivityForResult(intent, REQUEST)
             .subscribe(new Observer<ActivityResult>() {
                  @Override public void onCompleted() {}

                  @Override public void onError(Throwable e) {
                      // エラー処理
                  }

                  @Override public void onNext(ActivityResult result) {
                      // 何かする
                  }
            });        
}

subscribeする際にObserverを指定することで、エラーハンドルも行えます。

本当は上記のような処理だけで完結すれば良かったのですが、作成したライブラリには結局、onActivityResult が出てきてしまうので、今後の課題です。

public class MainActivity extends Activity {
    private RxActivityLauncher mLauncher;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mLauncher = RxActivityLauncher.from(this);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        // あまりイケてない。。呼ぶの忘れるかも。。。
        mLauncher.onActivityResult(requestCode, resultCode, data);
    }

    private void startOtherActivityUsingRxJava() {
    }
}

ライブラリの実装

小さなライブラリなのでポイントは少ないですが、このような処理が散在するパターンをリアクティブプログラミングで解決する場合は、 Subject を使うといいのではないかと思います。
(SubjectはObservableであり、Observerでもあるクラスです)

ライブラリの実装のメイン部分は次のようになっています。

public Observable<ActivityResult> startActivityForResult(Intent intent, int requestCode, Bundle options) {
    // アクティビティの開始(省略)
    return makeSubject(requestCode);
}

private Observable<ActivityResult> makeSubject(int requestCode) {
    PublishSubject<ActivityResult> subject = mSubjects.get(requestCode);
    if (subject == null) {
        subject = PublishSubject.create();
        mSubjects.put(requestCode, subject);
    }
    return subject;
}

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // 他のアクティビティからの結果を受け取ったらそのまま流す
    final PublishSubject<ActivityResult> subject = mSubjects.get(requestCode);
    if (subject == null) {
        return;
    }
    subject.onNext(new ActivityResult(resultCode, data));
    subject.onCompleted();
    // 完了したら必要なくなるので削除
    mSubjects.remove(requestCode);
}

#onActivityResult からもらった結果情報をObserverへ垂れ流しているだけです。このようにロジックというほどのものがほぼない場合、Subjectだけで完結できるので、簡単に書くことができました。

もう一つの課題として、unsubscribeをスマートにできたらなーと思っています。
(できればライブラリ側でなんとかしたい。RxLifeCycle使えばいいかもしれませんが、継承を増やしなくない。。。)

以上、RxJavaを使ったことがない方にもRxJavaの便利さが少しでも伝わればと思いまとめてみました。

Written with StackEdit.

73
66
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
73
66