web-dev-qa-db-ja.com

いつRxJava Observableを使うべきで、いつAndroid上で単純なコールバックを行うべきですか?

私は自分のアプリのネットワーキングに取り組んでいます。そこで、Square's Retrofit を試してみることにしました。私は彼らが単純な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?
            }
        });

私はここに何かが足りないのですか?それともObservablesを使うのは間違ったケースでしょうか。単純なコールバックよりもObservableを好むのはいつでしょうか。

更新

@Nielsが彼の答えやJake Whartonのサンプルプロジェクトで示したように、後付けの使用は上の例よりもずっと簡単です 202 。しかし、本質的に問題は同じままです - どちらか一方を使うべきなのはいつですか?

246
Martynas Jurkus

単純なネットワーキングのために、コールバックに対する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は微調整のみが必要ですが、Callbackの変形は複数の場所で調整する必要があります(各コールバックで、すべてのデータが取得されたかどうかを確認する必要があります)。

もう一つの例; Retrofitを使ってデータをロードするオートコンプリートフィールドを作りたい。 EditTextにTextChangedEventがあるたびにWebCallを実行したくはありません。速くタイプするとき、最後の要素だけが呼び出しを引き起こすべきです。 RxJavaでは、デバウンス演算子を使用できます。

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

Callbackの亜種は作成しませんが、これははるかに手間のかかる作業であることがわかります。

結論:データがストリームとして送信される場合、RxJavaは非常に優れています。 Retrofit Observableはストリーム上のすべての要素を同時にプッシュします。これは、Callbackと比較して、それ自体では特に役に立ちません。しかし、複数の要素がストリームにプッシュされている時間が異なり、タイミング関連の処理を行う必要がある場合は、RxJavaを使用するとコードのメンテナンス性が大幅に向上します。

332
Niels

Observableに関するものはすでにRetrofitで行われているので、コードは次のようになります。

api.getUserPhoto(photoId)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Photo>() {
         @Override
            public void call(Photo photo) {
                //save photo?
            }
     });
63
Niels

GetUserPhoto()の場合、RxJavaの利点はそれほど大きくありません。しかし、ユーザーの写真をすべて取得するときに別の例を見てみましょう。ただし、画像がPNGで、サーバー側でフィルタ処理を行うためにJSONにアクセスできない場合に限られます。

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) {
            // on main thread; callback for each photo, add them to a list or something.
            list.add(photo)
        }
    }, 
    new Action1<Throwable>() {
    @Override
        public void call(Throwable throwable) {
            // on main thread; something went wrong
            System.out.println("Error! " + throwable);
        }
    }, 
    new Action0() {
        @Override
        public void call() {
            // on main thread; all photo's loaded, time to show the list or something.
        }
    });

これでJSONはPhotoのリストを返します。それらを個々のアイテムにflatMapします。そうすることで、PNGではない写真を無視するためにfilterメソッドを使うことができるでしょう。その後、購読して、個々の写真ごとにコールバック、errorHandler、すべての行が完了したときのコールバックを取得します。

TLDRここがポイントです。コールバックは成功と失敗のコールバックのみを返します。 RxJavaオブザーバブルを使用すると、マップ、縮小、フィルタ処理など、さまざまなことを実行できます。

34
Niels

Rxjavaを使用すると、少ないコードでより多くのことを実行できます。

アプリにインスタント検索を実装するとしましょう。コールバックを使用すると、前のリクエストの購読を解除して新しいリクエストを購読することを心配しているので、自分で向きの変更を処理する必要があります。これは大量のコードであり、冗長すぎます。

Rxjavaを使えばとても簡単です。

public class PhotoModel{
  BehaviorSubject<Observable<Photo>> subject = BehaviorSubject.create(...);

  public void setUserId(String id){
   subject.onNext(Api.getUserPhoto(photoId));
  }

  public Observable<Photo> subscribeToPhoto(){
    return Observable.switchOnNext(subject);
  }
}

インスタント検索を実装したい場合は、TextChangeListenerをリッスンしてphotoModel.setUserId(EditText.getText());を呼び出すだけです。

フラグメントまたはアクティビティのonCreateメソッドで、photoModel.subscribeToPhoto()を返すObservableをサブスクライブすると、常に最新のObservable(要求)によって空になったアイテムを放出するObservableが返されます。

AndroidObservable.bindFragment(this, photoModel.subscribeToPhoto())
                 .subscribe(new Action1<Photo>(Photo photo){
      //Here you always receive the response of the latest query to the server.
                  });

また、たとえばPhotoModelがSingletonの場合は、購読のタイミングに関係なく、BehaviorSubjectが最後のサーバー応答を発行するため、方向の変更について心配する必要はありません。

このコード行を使用して、即時検索を実装し、方向の変更を処理しました。あなたはこれをより少ないコードのコールバックで実装できると思いますか?疑わしい。

27

私たちは通常、次のような論理を取ります。

  1. それが単純な1応答の呼び出しであるならば、それからCallbackかFutureが優れています。
  2. それが複数の応答を伴う呼び出し(ストリーム)である場合、または異なる呼び出し間で複雑な相互作用がある場合(@Niels ' answer を参照)、Observablesが優れています。
2
Dzmitry Lazerka

あなたが車輪を再発明しているように見えます、あなたがしていることは改装で既に実行されています。

たとえば、レトロタイプの RestAdapterTest.Java 、Observableを戻り型として インターフェースを定義 、そして それを使用 のようになります。

0
Samuel Gruetter

他の答えのサンプルと結論から、単純な1ステップまたは2ステップのタスクに大きな違いはないと思います。しかし、コールバックは単純明快です。 RxJavaはもっと複雑で、単純な作業には大きすぎます。による3番目の解決策があります。 AbacusUtil 。上記のユースケースを3つのソリューションすべてで実装してみましょう。コールバック、RxJava、CompletableFuture(AbacusUtil)を Retrolambda

ネットワークから写真を取得し、デバイスに保存/表示します。

// By Callback
api.getUserPhoto(userId, new Callback<Photo>() {
    @Override
    public void onResponse(Call<Photo> call, Response<Photo> response) {
        save(response.body()); // or update view on UI thread.
    }

    @Override
    public void onFailure(Call<Photo> call, Throwable t) {
        // show error message on UI or do something else.
    }
});

// By RxJava
api.getUserPhoto2(userId) //
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(photo -> {
            save(photo); // or update view on UI thread.
        }, error -> {
            // show error message on UI or do something else.
        });

// By Thread pool executor and CompletableFuture.
TPExecutor.execute(() -> api.getUserPhoto(userId))
        .thenRunOnUI((photo, error) -> {
            if (error != null) {
                // show error message on UI or do something else.
            } else {
                save(photo); // or update view on UI thread.
            }
        });

ユーザーの詳細と写真を並行して読み込む

// By Callback
// ignored because it's little complicated

// By RxJava
Observable.Zip(api.getUserDetails2(userId), api.getUserPhoto2(userId), (details, photo) -> Pair.of(details, photo))
        .subscribe(p -> {
            // Do your task.
        });

// By Thread pool executor and CompletableFuture.
TPExecutor.execute(() -> api.getUserDetails(userId))
          .runOnUIAfterBoth(TPExecutor.execute(() -> api.getUserPhoto(userId)), p -> {
    // Do your task
});
0
user_3380739

私は個人的に私は私が私が私がデータにフィルターをかけ、地図を描くか、またはそのような何かをしなければならない場合や、以前の呼び出し応答に基づいて別のAPI呼び出しをしなければならない場合にAPI応答を得るために好む

0
Reza