Kapan harus menggunakan RxJava Observable dan kapan Callback sederhana di Android?


260

Saya sedang mengerjakan jaringan untuk aplikasi saya. Jadi saya memutuskan untuk mencoba Square's Retrofit . Saya melihat bahwa mereka mendukung sederhanaCallback

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

dan RxJava Observable

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

Keduanya terlihat sangat mirip pada pandangan pertama, tetapi ketika sampai pada implementasi itu menjadi menarik ...

Sementara dengan implementasi callback sederhana akan terlihat mirip dengan ini:

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

yang cukup sederhana dan mudah. Dan dengan Observableitu cepat menjadi verbose dan cukup rumit.

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());
}

Dan bukan itu. Anda masih harus melakukan sesuatu seperti ini:

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?
            }
        });

Apakah saya melewatkan sesuatu di sini? Atau apakah ini kasus yang salah untuk digunakan Observable? Kapan seseorang harus / Observablelebih suka daripada Callback sederhana?

Memperbarui

Menggunakan retrofit jauh lebih sederhana daripada contoh di atas seperti yang ditunjukkan @Niels dalam jawabannya atau dalam contoh proyek Jake Wharton U2020 . Tetapi pada dasarnya pertanyaannya tetap sama - kapan satu harus menggunakan satu cara atau yang lain?


dapatkah Anda memperbarui tautan ke file yang sedang Anda bicarakan di U2020
letroll

Masih bekerja ...
Martynas Jurkus

4
Sobat, saya memiliki pikiran yang sama persis ketika saya membaca RxJava adalah hal baru. Saya membaca contoh retrofit (karena saya sangat akrab dengannya) dari permintaan sederhana dan itu adalah sepuluh atau lima belas baris kode dan reaksi pertama saya adalah Anda pasti bercanda = /. Saya juga tidak tahu bagaimana ini menggantikan bus peristiwa, karena bus peristiwa memisahkan Anda dari yang dapat diamati dan rxjava memperkenalkan kembali kopling, kecuali saya salah.
Lo-Tan

Jawaban:


348

Untuk hal-hal jaringan yang sederhana, kelebihan RxJava dibandingkan Callback sangat terbatas. Contoh getUserPhoto sederhana:

RxJava:

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

Telepon balik:

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

Varian RxJava tidak jauh lebih baik daripada varian Callback. Untuk saat ini, mari kita abaikan penanganan kesalahan. Mari kita ambil daftar foto:

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)
        }
    });

Telepon balik:

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);
            }
        }
    }
});

Sekarang, varian RxJava masih tidak lebih kecil, meskipun dengan Lambdas akan lebih dekat dengan varian Callback. Selain itu, jika Anda memiliki akses ke umpan JSON, akan agak aneh untuk mengambil semua foto saat Anda hanya menampilkan PNG. Sesuaikan feed agar hanya menampilkan PNG.

Kesimpulan pertama

Itu tidak membuat basis kode Anda lebih kecil ketika Anda memuat JSON sederhana yang Anda siapkan dalam format yang tepat.

Sekarang, mari kita buat hal-hal sedikit lebih menarik. Katakanlah Anda tidak hanya ingin mengambil userPhoto, tetapi Anda memiliki kloning-Instagram, dan Anda ingin mengambil 2 JSON: 1. getUserDetails () 2. getUserPhotos ()

Anda ingin memuat kedua JSON ini secara paralel, dan ketika keduanya dimuat, halaman tersebut harus ditampilkan. Varian panggilan balik akan menjadi sedikit lebih sulit: Anda harus membuat 2 panggilan balik, menyimpan data dalam aktivitas, dan jika semua data dimuat, tampilkan halaman:

Telepon balik:

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) {
            }
        });

Kami pergi ke suatu tempat! Kode RxJava sekarang sebesar opsi callback. Kode RxJava lebih kuat; Pikirkan apa yang akan terjadi jika kita membutuhkan JSON ketiga untuk dimuat (seperti Video terbaru)? RxJava hanya perlu sedikit penyesuaian, sedangkan varian Callback perlu disesuaikan di beberapa tempat (pada setiap panggilan balik kita perlu memeriksa apakah semua data diambil).

Contoh lain; kami ingin membuat bidang pelengkapan otomatis, yang memuat data menggunakan Retrofit. Kami tidak ingin melakukan panggilan web setiap kali EditText memiliki TextChangedEvent. Saat mengetik cepat, hanya elemen terakhir yang harus memicu panggilan. Pada RxJava kita dapat menggunakan operator debounce:

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

Saya tidak akan membuat varian Callback tetapi Anda akan mengerti ini jauh lebih sulit.

Kesimpulan: RxJava sangat baik ketika data dikirim sebagai aliran. Retrofit Observable mendorong semua elemen pada aliran pada saat yang bersamaan. Ini tidak terlalu berguna dalam dirinya sendiri dibandingkan dengan Callback. Tetapi ketika ada beberapa elemen yang didorong pada aliran dan waktu yang berbeda, dan Anda perlu melakukan hal-hal yang berhubungan dengan waktu, RxJava membuat kode jauh lebih mudah dikelola.


6
Kelas apa yang Anda gunakan untuk inputObservable? Saya memiliki banyak masalah dengan debouncing dan ingin belajar lebih banyak tentang solusi ini.
Migore

@Migore proyek RxBinding memiliki modul "Platform binding" yang menyediakan kelas seperti RxView, RxTextViewdll yang dapat digunakan untuk inputObservable.
badai salju

@Niels, bisakah Anda menjelaskan cara menambahkan penanganan kesalahan? dapatkah Anda membuat pelanggan dengan onCompleted, onError dan onNext jika Anda flatMap, Filter dan kemudian berlangganan? Terima kasih banyak atas penjelasan besarnya.
Nicolas Jafelle

4
Ini adalah contoh yang luar biasa!
Ye Lin Aung

3
Penjelasan terbaik!
Denis Nek

66

Hal-hal yang Dapat Diamati sudah dilakukan di Retrofit, jadi kodenya bisa seperti ini:

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

5
Ya, tetapi pertanyaannya adalah kapan harus memilih yang lain?
Christopher Perry

7
Jadi bagaimana ini lebih baik daripada panggilan balik sederhana?
Martynas Jurkus

14
@MartynasJurkus dapat diamati dapat berguna jika Anda ingin rantai beberapa fungsi. Misalnya, penelepon ingin mendapatkan foto yang dipotong ke dimensi 100x100. Api dapat mengembalikan foto ukuran apa pun, sehingga Anda dapat memetakan getUserPhoto yang dapat diamati ke ResizedPhotoObservable lainnya - penelepon hanya akan diberi tahu saat pengubahan ukuran dilakukan. Jika Anda tidak perlu menggunakannya, jangan memaksanya.
ataulm

2
Satu umpan balik kecil. Tidak perlu menelepon .subscribeOn (Schedulers.io ()) karena RetroFit sudah menangani ini - github.com/square/retrofit/issues/430 (lihat balasan Jake)
hiBrianLee

4
@MartynasJurkus juga menambahkan hal-hal yang dikatakan oleh ataulm, tidak seperti CallbackAnda dapat berhenti berlangganan Observeable, karena itu menghindari masalah daur hidup umum.
Dmitry Zaytsev

35

Dalam kasus getUserPhoto () keuntungan untuk RxJava tidak bagus. Tapi mari kita ambil contoh lain ketika Anda akan mendapatkan semua foto untuk pengguna, tetapi hanya ketika gambar adalah PNG, dan Anda tidak memiliki akses ke JSON untuk melakukan pemfilteran di sisi server.

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.
        }
    });

Sekarang JSON mengembalikan daftar Photo. Kami akan flatMap mereka ke item individual. Dengan melakukannya, kita akan dapat menggunakan metode filter untuk mengabaikan foto yang bukan PNG. Setelah itu, kami akan berlangganan, dan mendapatkan panggilan balik untuk setiap foto, errorHandler, dan panggilan balik ketika semua baris telah selesai.

Titik TLDR di sini adalah; panggilan balik hanya mengembalikan Anda panggilan balik untuk keberhasilan dan kegagalan; Observasi RxJava memungkinkan Anda melakukan peta, mengurangi, memfilter, dan banyak hal lainnya.


Berlangganan pertama dan mengamati tidak diperlukan dengan retrofit. Ini akan melakukan panggilan jaringan secara tidak sinkron dan memberitahukan pada utas yang sama dengan penelepon. Ketika Anda menambahkan RetroLambda juga untuk mendapatkan ekspresi lambda, itu menjadi jauh lebih baik.

tetapi saya dapat melakukannya (filter gambar adalah PNG) di .subscribe () Mengapa saya harus menggunakan filter? dan tidak perlu dalam flatmap
SERG

3
3 jawaban dari @Niels. Yang pertama menjawab pertanyaan itu. Tidak ada manfaatnya untuk yang ke-2
Raymond Chenon

jawaban yang sama dengan yang pertama
Mayank Sharma

27

Dengan rxjava Anda dapat melakukan lebih banyak hal dengan lebih sedikit kode.

Mari kita asumsikan bahwa Anda ingin menerapkan pencarian instan di aplikasi Anda. Dengan panggilan balik, Anda khawatir tentang berhenti berlangganan permintaan sebelumnya dan berlangganan yang baru, menangani perubahan orientasi sendiri ... Saya pikir itu banyak kode dan terlalu bertele-tele.

Dengan rxjava sangat sederhana.

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);
  }
}

jika Anda ingin menerapkan pencarian instan, Anda hanya perlu mendengarkan TextChangeListener dan menelepon photoModel.setUserId(EditText.getText());

Dalam metode Fragmen atau aktivitas onCreate Anda berlangganan Observable yang mengembalikan photoModel.subscribeToPhoto (), ia mengembalikan Observable yang selalu memancarkan item yang dipancarkan oleh Observable terbaru (permintaan).

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

Juga, jika PhotoModel adalah Singleton, misalnya, Anda tidak perlu khawatir tentang perubahan orientasi, karena BehaviorSubject memancarkan respons server terakhir, terlepas dari kapan Anda berlangganan.

Dengan baris kode ini kami telah menerapkan pencarian instan dan menangani perubahan orientasi. Apakah Anda pikir Anda dapat menerapkan ini dengan panggilan balik dengan kode lebih sedikit? Aku meragukan itu.


Tidakkah Anda berpikir menjadikan kelas PhotoModel sebagai Singleton itu sendiri merupakan batasan? Bayangkan itu bukan profil pengguna tetapi kumpulan foto untuk banyak pengguna, bukankah kita hanya akan mendapatkan foto pengguna yang terakhir diminta? Ditambah meminta data pada setiap perubahan orientasi terdengar agak aneh bagi saya, bagaimana menurut Anda?
Farid

2

Kami biasanya menggunakan logika berikut:

  1. Jika itu panggilan satu respons sederhana, maka Callback atau Future lebih baik.
  2. Jika itu adalah panggilan dengan beberapa respons (streaming), atau ketika ada interaksi yang kompleks antara panggilan yang berbeda (lihat jawaban @Niels ' ), maka Observable lebih baik.

1

Dengan sampel dan kesimpulan dalam jawaban lain, saya pikir tidak ada perbedaan besar untuk tugas sederhana satu atau dua langkah. Namun, Callback sederhana dan mudah. RxJava lebih rumit dan terlalu besar untuk tugas sederhana. Ada solusi ketiga oleh: AbacusUtil . Biarkan saya menerapkan kasus penggunaan di atas dengan ketiga solusi: Callback, RxJava, CompletableFuture (AbacusUtil) dengan Retrolambda :

Ambil foto dari jaringan dan simpan / tampilkan di perangkat:

// 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.
            }
        });

Muat detail pengguna dan foto secara paralel

// 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
});

4
OP tidak meminta saran untuk perpustakaan baru
forresthopkinsa

1

Saya pribadi lebih suka menggunakan Rx untuk mendapatkan respons api dalam kasus yang harus saya lakukan filter, peta atau sesuatu seperti itu pada data Atau dalam kasus bahwa saya harus melakukan panggilan api lain berdasarkan respons panggilan sebelumnya


0

Sepertinya Anda menciptakan kembali roda, apa yang Anda lakukan sudah diterapkan di retrofit.

Sebagai contoh, Anda bisa melihat RestAdapterTest.java retrofit , di mana mereka mendefinisikan antarmuka dengan Observable sebagai tipe pengembalian, dan kemudian menggunakannya .


Terima kasih atas jawabannya. Saya mengerti apa yang Anda katakan, tetapi saya belum yakin bagaimana saya akan mengimplementasikannya. Bisakah Anda memberikan contoh sederhana menggunakan implementasi retrofit yang ada sehingga saya bisa memberi Anda hadiah?
Yehosef

@Yehosef apakah seseorang menghapus komentar mereka atau Anda berpura-pura menjadi OP? ))
Farid

Anda dapat memberi hadiah atas pertanyaan orang lain. Ketika saya menanyakan hal ini, saya benar-benar membutuhkan jawaban untuk pertanyaan - jadi saya menawarkan hadiah. Jika Anda mengarahkan pada hadiah karunia, Anda akan melihat bahwa itu diberikan oleh saya.
Yehosef

0

Saat Anda membuat aplikasi untuk bersenang-senang, proyek kesayangan, atau POC atau prototipe ke-1 Anda menggunakan kelas inti android / java sederhana seperti callback, tugas async, looper, utas, dll. Mereka mudah digunakan dan tidak memerlukan apa pun Integrasi lib pihak ketiga. Integrasi perpustakaan besar hanya untuk membangun proyek kecil yang tidak dapat diubah adalah tidak masuk akal ketika hal serupa dapat dilakukan dengan benar.

Namun, ini seperti pisau yang sangat tajam. Menggunakan ini dalam lingkungan produksi selalu keren tetapi juga mereka akan memiliki konsekuensi. Sulit untuk menulis kode konkuren yang aman jika Anda tidak berpengalaman dengan prinsip pengkodean Bersih dan SOLID. Anda harus mempertahankan arsitektur yang tepat untuk memfasilitasi perubahan di masa depan dan meningkatkan produktivitas tim.

Di sisi lain, pustaka konkurensi seperti RxJava, Co-rutin dll dicoba dan diuji lebih dari satu miliar kali untuk membantu menulis kode konkursi yang siap-produksi. Sekarang lagi, bukan dengan menggunakan pustaka-pustaka ini Anda tidak menulis kode bersamaan atau mengabstraksi semua logika konkurensi. Kamu masih. Tapi sekarang, itu terlihat dan memberlakukan pola yang jelas untuk menulis kode bersamaan di seluruh basis kode dan yang lebih penting di seluruh tim pengembangan Anda.

Ini adalah manfaat utama menggunakan kerangka kerja konkurensi alih-alih kelas inti lama sederhana yang berhubungan dengan konkurensi mentah. Namun, jangan salah paham. Saya sangat percaya dalam membatasi dependensi perpustakaan eksternal tetapi dalam kasus khusus ini, Anda harus membangun kerangka kerja kustom untuk basis kode Anda yang merupakan tugas yang memakan waktu dan dapat dilakukan hanya setelah pengalaman sebelumnya. Oleh karena itu, kerangka kerja konkurensi lebih disukai daripada menggunakan kelas biasa seperti callback, dll.


TL'DR

Jika Anda sudah menggunakan RxJava untuk pengkodean bersamaan di seluruh basis kode, cukup gunakan RxJava Observable / Flowable. Pertanyaan bijak untuk ditanyakan adalah apakah saya harus menggunakan Observables untuk Flowables. Jika tidak, lanjutkan dan gunakan callable.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.