1. ホーム
  2. アンドロイド

[解決済み】AndroidでRxJava ObservableとシンプルなCallbackを使うべきタイミングは?

2022-04-05 11:47:06

質問

私のアプリのネットワーク構築に取り組んでいます。そこで、Squareの レトロフィット . シンプルな Callback

@GET("/user/{id}/photo")
void getUserPhoto(@Path("id") int id, Callback<Photo> cb);

とRxJavaの Observable

@GET("/user/{id}/photo")
Observable<Photo> getUserPhoto(@Path("id") int id);

どちらも一見するとよく似ていますが、実装になると面白いことに......。

単純なコールバックの実装では、このような感じになります。

api.getUserPhoto(photoId, new Callback<Photo>() {
    @Override
    public void onSuccess() {
    }
});

という、非常にシンプルでわかりやすいものです。そして Observable は、すぐに冗長になり、かなり複雑になります。

public Observable<Photo> getUserPhoto(final int photoId) {
    return Observable.create(new Observable.OnSubscribeFunc<Photo>() {
        @Override
        public Subscription onSubscribe(Observer<? super Photo> observer) {
            try {
                observer.onNext(api.getUserPhoto(photoId));
                observer.onCompleted();
            } catch (Exception e) {
                observer.onError(e);
            }

            return Subscriptions.empty();
        }
    }).subscribeOn(Schedulers.threadPoolForIO());
}

そして、それだけではありません。まだこんなことをしなければならないのです。

Observable.from(photoIdArray)
        .mapMany(new Func1<String, Observable<Photo>>() {
            @Override
            public Observable<Photo> call(Integer s) {
                return getUserPhoto(s);
            }
        })
        .subscribeOn(Schedulers.threadPoolForIO())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Action1<Photo>() {
            @Override
            public void call(Photo photo) {
                //save photo?
            }
        });

何か見落としているのでしょうか?それとも、このケースは Observable s? どのような場合に Observable を単純なCallbackに置き換えるか?

更新情報

retrofitの使用方法は、@Nielsの回答やJake Whartonのサンプルプロジェクトにあるように、上記の例よりもずっとシンプルです。 U2020 . しかし、本質的な疑問は変わりません - いつ、どちらかの方法を使うべきなのでしょうか?

どのように解決するのか?

単純なネットワーキングの場合、コールバックに対するRxJavaの利点は非常に限られています。単純なgetUserPhotoの例です。

RxJavaです。

api.getUserPhoto(photoId)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Photo>() {
            @Override
            public void call(Photo photo) {
               // do some stuff with your photo 
            }
     });

コールバック

api.getUserPhoto(photoId, new Callback<Photo>() {
    @Override
    public void onSuccess(Photo photo, Response response) {
    }
});

RxJavaのバリアントはCallbackのバリアントよりあまり良くはありません。とりあえず、エラー処理については無視しましょう。 写真のリストを取ってみよう。

RxJavaです。

api.getUserPhotos(userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<List<Photo>, Observable<Photo>>() {
    @Override
    public Observable<Photo> call(List<Photo> photos) {
         return Observable.from(photos);
    }
})
.filter(new Func1<Photo, Boolean>() {
    @Override
    public Boolean call(Photo photo) {
         return photo.isPNG();
    }
})
.subscribe(
    new Action1<Photo>() {
    @Override
        public void call(Photo photo) {
            list.add(photo)
        }
    });

コールバック

api.getUserPhotos(userId, new Callback<List<Photo>>() {
    @Override
    public void onSuccess(List<Photo> photos, Response response) {
        List<Photo> filteredPhotos = new ArrayList<Photo>();
        for(Photo photo: photos) {
            if(photo.isPNG()) {
                filteredList.add(photo);
            }
        }
    }
});

さて、RxJavaのバリエーションはまだ小さくはありませんが、Lambdasを使えばCallbackのバリエーションに近くなります。 さらに、JSONフィードにアクセスできるのであれば、PNGだけを表示しているときにすべての写真を取得するのはちょっと変です。PNGだけを表示するようにフィードを調整すればよいのです。

最初の結論

正しいフォーマットで用意した簡単なJSONを読み込むだけでは、コードベースは小さくならない。

さて、もう少し面白いことをやってみましょう。userPhotoを取得したいだけでなく、Instagramクローンを持っていて、2つのJSONを取得したいとします。 1. getUserDetails() 2. getUserPhotos()

この2つのJSONを並行して読み込み、両方が読み込まれたらページを表示させるようにします。 コールバックの変形は少し難しくなります。2つのコールバックを作成し、データをアクティビティに保存し、すべてのデータが読み込まれたら、ページを表示する必要があります。

コールバック

api.getUserDetails(userId, new Callback<UserDetails>() {
    @Override
    public void onSuccess(UserDetails details, Response response) {
        this.details = details;
        if(this.photos != null) {
            displayPage();
        }
    }
});

api.getUserPhotos(userId, new Callback<List<Photo>>() {
    @Override
    public void onSuccess(List<Photo> photos, Response response) {
        this.photos = photos;
        if(this.details != null) {
            displayPage();
        }
    }
});

RxJavaです。

private class Combined {
    UserDetails details;
    List<Photo> photos;
}


Observable.zip(api.getUserDetails(userId), api.getUserPhotos(userId), new Func2<UserDetails, List<Photo>, Combined>() {
            @Override
            public Combined call(UserDetails details, List<Photo> photos) {
                Combined r = new Combined();
                r.details = details;
                r.photos = photos;
                return r;
            }
        }).subscribe(new Action1<Combined>() {
            @Override
            public void call(Combined combined) {
            }
        });

どこかで見たことがあるような気がします。RxJavaのコードは、コールバック・オプションと同じ大きさになりました。RxJavaのコードはより堅牢になりました。 もし、3つ目のJSONを読み込む必要があったらどうなるか考えてみてください(最新の動画のような)。RxJavaはわずかな調整で済みますが、コールバックバリアントは複数の場所で調整する必要があります(各コールバックで、すべてのデータが取得されたかどうかをチェックする必要があります)。

別の例として、Retrofitを使用してデータを読み込むオートコンプリートフィールドを作成したいと思います。 EditText に TextChangedEvent が発生するたびに webcall を実行するのは面倒です。速く入力するときは、最後の要素だけが呼び出しのトリガーになればよいのです。 RxJavaでは、debounce演算子を使うことができます。

inputObservable.debounce(1, TimeUnit.SECONDS).subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                // use Retrofit to create autocompletedata
            }
        });

コールバックのバリアントは作りませんが、これはもっと大変な作業であることはご理解いただけると思います。

結論 RxJavaは、データがストリームとして送信される場合に非常に優れています。Retrofit Observableは、ストリーム上のすべての要素を同時にプッシュします。 これ自体はCallbackと比較して特に便利というわけではありません。しかし、ストリームにプッシュされる要素が複数あり、時間もまちまちで、タイミングに関することをする必要があるとき、RxJavaを使うとコードのメンテナンス性がぐっと上がります。