Poznaj RxJava: brakującą bibliotekę programowania reaktywnego na Androida

Opublikowany: 2022-03-11

Jeśli jesteś programistą Androida, prawdopodobnie słyszałeś o RxJava. Jest to jedna z najczęściej omawianych bibliotek umożliwiających programowanie reaktywne w programowaniu na Androida. Jest reklamowany jako platforma do uproszczenia współbieżności/asynchronicznych zadań związanych z programowaniem mobilnym.

Ale… czym jest RxJava i jak „uproszcza” rzeczy?

Funkcjonalne programowanie reaktywne dla Androida: wprowadzenie do RxJava

Uwolnij swojego Androida od zbyt wielu wątków Java dzięki RxJava.
Ćwierkać

Chociaż istnieje wiele zasobów już dostępnych online wyjaśniających, czym jest RxJava, w tym artykule moim celem jest przedstawienie podstawowego wprowadzenia do RxJava, a konkretnie, jak pasuje do rozwoju Androida. Podam też kilka konkretnych przykładów i sugestii, jak można go zintegrować z nowym lub istniejącym projektem.

Dlaczego warto rozważyć RxJava?

W swej istocie RxJava upraszcza programowanie, ponieważ podnosi poziom abstrakcji wokół wątków. Oznacza to, że jako programista nie musisz się zbytnio martwić o szczegóły wykonywania operacji, które powinny wystąpić w różnych wątkach. Jest to szczególnie atrakcyjne, ponieważ tworzenie wątków jest trudne, a jeśli nie jest prawidłowo zaimplementowane, może powodować niektóre z najtrudniejszych błędów do debugowania i naprawy.

To prawda, że ​​nie oznacza to, że RxJava jest kuloodporny, jeśli chodzi o wątki i nadal ważne jest, aby zrozumieć, co dzieje się za kulisami; jednak RxJava może zdecydowanie ułatwić Ci życie.

Spójrzmy na przykład.

Połączenie sieciowe — RxJava kontra AsyncTask

Załóżmy, że chcemy uzyskać dane przez sieć i w rezultacie zaktualizować interfejs użytkownika. Jednym ze sposobów, aby to zrobić, jest (1) utworzenie wewnętrznej podklasy AsyncTask w naszym Activity / Fragment , (2) wykonanie operacji sieciowej w tle oraz (3) pobranie wyniku tej operacji i zaktualizowanie interfejsu użytkownika w głównym wątku .

 public class NetworkRequestTask extends AsyncTask<Void, Void, User> { private final int userId; public NetworkRequestTask(int userId) { this.userId = userId; } @Override protected User doInBackground(Void... params) { return networkService.getUser(userId); } @Override protected void onPostExecute(User user) { nameTextView.setText(user.getName()); // ...set other views } } private void onButtonClicked(Button button) { new NetworkRequestTask(123).execute() }

Choć może się to wydawać nieszkodliwe, to podejście ma pewne problemy i ograniczenia. Mianowicie, wycieki pamięci/kontekstu są łatwo tworzone, ponieważ NetworkRequestTask jest klasą wewnętrzną, a zatem zawiera niejawne odniesienie do klasy zewnętrznej. A co, jeśli chcemy połączyć kolejną długą operację po połączeniu sieciowym? Musielibyśmy zagnieździć dwa AsyncTask , co może znacznie zmniejszyć czytelność.

W przeciwieństwie do tego, podejście RxJava do wykonywania połączenia sieciowego może wyglądać mniej więcej tak:

 private Subscription subscription; private void onButtonClicked(Button button) { subscription = networkService.getObservableUser(123) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<User>() { @Override public void call(User user) { nameTextView.setText(user.getName()); // ... set other views } }); } @Override protected void onDestroy() { if (subscription != null && !subscription.isUnsubscribed()) { subscription.unsubscribe(); } super.onDestroy(); }

Stosując takie podejście, rozwiązujemy problem (potencjalnych wycieków pamięci spowodowanych przez działający wątek przechowujący referencję do kontekstu zewnętrznego) poprzez zachowanie referencji do zwróconego obiektu Subscription . Ten obiekt Subscription jest następnie powiązany z metodą #onDestroy() obiektu Activity / Fragment , aby zagwarantować, że operacja Action1 Action1#call nie zostanie wykonana, gdy Activity / Fragment wymaga zniszczenia.

Zauważ również, że zwracany typ #getObservableUser(...) (tj. Observable<User> ) jest powiązany z kolejnymi wywołaniami do niego. Dzięki temu płynnemu interfejsowi API jesteśmy w stanie rozwiązać drugi problem związany z używaniem AsyncTask , który polega na tym, że umożliwia dalsze łączenie połączeń sieciowych/długich operacji. Całkiem fajnie, co?

Zanurzmy się głębiej w niektóre koncepcje RxJava.

Obserwowalny, obserwator i operator — 3 O rdzenia RxJava

W świecie RxJava wszystko można modelować jako strumienie. Strumień emituje elementy w czasie, a każdą emisję można zużyć/obserwować.

Jeśli się nad tym zastanowić, strumień nie jest nową koncepcją: zdarzenia kliknięć mogą być strumieniem, aktualizacje lokalizacji mogą być strumieniem, powiadomienia push mogą być strumieniem i tak dalej.

W świecie RxJava wszystko można modelować jako strumienie.

Abstrakcja strumienia jest implementowana przez 3 podstawowe konstrukcje, które lubię nazywać „3 O”; mianowicie: Obserwowalny, Obserwator i O perator . Obserwowalny emituje elementy (strumień); a Obserwator konsumuje te przedmioty. Emisje z obiektów obserwowalnych można dalej modyfikować, przekształcać i manipulować przez łączenie wywołań operatorów .

Zauważalny

Obserwowalny to abstrakcja strumienia w RxJava. Jest podobny do iteratora pod tym względem, że przy danej sekwencji iteruje i tworzy te elementy w uporządkowany sposób. Konsument może następnie wykorzystać te elementy za pośrednictwem tego samego interfejsu, niezależnie od podstawowej kolejności.

Powiedzmy, że chcemy emitować liczby 1, 2, 3 w tej kolejności. W tym celu możemy użyć metody Observable<T>#create(OnSubscribe<T>) .

 Observable<Integer> observable = Observable.create(new Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { subscriber.onNext(1); subscriber.onNext(2); subscriber.onNext(3); subscriber.onCompleted(); } });

Wywołanie subscriber.onNext(Integer) powoduje emisję elementu w strumieniu, a po zakończeniu emisji strumienia wywoływana jest funkcja subscriber.onCompleted() .

To podejście do tworzenia Observable jest dość szczegółowe. Z tego powodu istnieją wygodne metody tworzenia instancji Observable, które powinny być preferowane w prawie wszystkich przypadkach.

Najprostszym sposobem na utworzenie Observable jest użycie Observable#just(...) . Jak sugeruje nazwa metody, po prostu emituje elementy, które przekazujesz do niej jako argumenty metody.

 Observable.just(1, 2, 3); // 1, 2, 3 will be emitted, respectively

Obserwator

Następnym składnikiem strumienia obserwowalnego jest obserwator (lub obserwatorzy), który go subskrybuje. Obserwatorzy są powiadamiani, gdy w strumieniu dzieje się coś „ciekawego”. Obserwatorzy są powiadamiani za pośrednictwem następujących wydarzeń:

  • Observer#onNext(T) - wywoływany, gdy element jest emitowany ze strumienia
  • Observable#onError(Throwable) - wywoływany, gdy w strumieniu wystąpił błąd
  • Observable#onCompleted() — wywoływane, gdy strumień kończy emisję elementów.

Aby zasubskrybować strumień, po prostu wywołaj Observable<T>#subscribe(...) i przekaż w wystąpieniu Observer.

 Observable<Integer> observable = Observable.just(1, 2, 3); observable.subscribe(new Observer<Integer>() { @Override public void onCompleted() { Log.d("Test", "In onCompleted()"); } @Override public void onError(Throwable e) { Log.d("Test", "In onError()"); } @Override public void onNext(Integer integer) { Log.d("Test", "In onNext():" + integer); } });

Powyższy kod wyemituje w Logcat:

 In onNext(): 1 In onNext(): 2 In onNext(): 3 In onNext(): 4 In onCompleted()

Mogą również wystąpić sytuacje, w których nie jesteśmy już zainteresowani emisją Observable. Jest to szczególnie istotne w systemie Android, gdy na przykład Activity / Fragment musi zostać odzyskane w pamięci.

Aby przestać obserwować pozycje, wystarczy wywołać Subscription#unsubscribe() na zwróconym obiekcie Subscription.

 Subscription subscription = someInfiniteObservable.subscribe(new Observer<Integer>() { @Override public void onCompleted() { // ... } @Override public void onError(Throwable e) { // ... } @Override public void onNext(Integer integer) { // ... } }); // Call unsubscribe when appropriate subscription.unsubscribe();

Jak widać w powyższym fragmencie kodu, po zasubskrybowaniu Observable, przechowujemy odniesienie do zwróconego obiektu Subscription i później wywołujemy Subscription subscription#unsubscribe() , gdy jest to konieczne. W systemie Android najlepiej jest to wywołać w Activity#onDestroy() lub Fragment#onDestroy() .

Operator

Przedmioty emitowane przez Observable mogą być przekształcane, modyfikowane i filtrowane przez Operatory przed powiadomieniem subskrybowanych obiektów Observer. Niektóre z najczęstszych operacji występujących w programowaniu funkcjonalnym (takie jak mapowanie, filtrowanie, zmniejszanie itp.) można również zastosować do strumienia obserwowalnego. Spójrzmy na mapę jako przykład:

 Observable.just(1, 2, 3, 4, 5).map(new Func1<Integer, Integer>() { @Override public Integer call(Integer integer) { return integer * 3; } }).subscribe(new Observer<Integer>() { @Override public void onCompleted() { // ... } @Override public void onError(Throwable e) { // ... } @Override public void onNext(Integer integer) { // ... } });

Powyższy fragment kodu wziąłby każdą emisję z obserwowalnego i pomnożyłby każdą przez 3, tworząc odpowiednio strumień 3, 6, 9, 12, 15. Zastosowanie Operatora zazwyczaj zwraca jako wynik inny Observable, co jest wygodne, ponieważ pozwala nam łączyć wiele operacji w celu uzyskania pożądanego rezultatu.

Biorąc pod uwagę powyższy strumień, powiedzmy, że chcieliśmy otrzymywać tylko liczby parzyste. Można to osiągnąć, łącząc operację filtrowania .

 Observable.just(1, 2, 3, 4, 5).map(new Func1<Integer, Integer>() { @Override public Integer call(Integer integer) { return integer * 3; } }).filter(new Func1<Integer, Boolean>() { @Override public Boolean call(Integer integer) { return integer % 2 == 0; } }).subscribe(new Observer<Integer>() { @Override public void onCompleted() { // ... } @Override public void onError(Throwable e) { // ... } @Override public void onNext(Integer integer) { // ... } });

Istnieje wiele operatorów wbudowanych w zestaw narzędzi RxJava, które modyfikują strumień Observable; jeśli możesz wymyślić sposób na modyfikację strumienia, są szanse, że jest do tego Operator. W przeciwieństwie do większości dokumentacji technicznej, czytanie dokumentacji RxJava/ReactiveX jest dość proste i rzeczowe. Do każdego operatora w dokumentacji dołączona jest wizualizacja, w jaki sposób Operator wpływa na strumień. Te wizualizacje nazywane są „marmurowymi diagramami”.

Oto jak hipotetyczny Operator zwany flipem może być modelowany na diagramie kulkowym:

Przykład, w jaki sposób hipotetyczny Operator zwany flipem może być modelowany za pomocą wykresu kulkowego.

Wielowątkowość z RxJava

Kontrolowanie wątku, w którym występują operacje w łańcuchu Observable, odbywa się poprzez określenie harmonogramu, w którym powinien wystąpić operator. Zasadniczo można myśleć o harmonogramie jako o puli wątków, której operator będzie używał i uruchamiał, jeśli zostanie określony. Domyślnie, jeśli nie podano takiego harmonogramu, łańcuch Observable będzie działał w tym samym wątku, w którym wywoływana jest funkcja Observable#subscribe(...) . W przeciwnym razie Harmonogram może być określony przez Observable#subscribeOn(Scheduler) i/lub Observable#observeOn(Scheduler) , gdzie zaplanowana operacja nastąpi w wątku wybranym przez Harmonogram.

Kluczową różnicą między tymi dwiema metodami jest to, że Observable#subscribeOn(Scheduler) instruuje źródło Observable, na którym harmonogramie powinien działać. Łańcuch będzie nadal działał w wątku z harmonogramu określonego w Observable#subscribeOn(Scheduler) , dopóki nie zostanie wykonane wywołanie Observable#observeOn(Scheduler) z innym harmonogramem. Kiedy takie wywołanie jest wykonywane, wszyscy obserwatorzy stamtąd (tj. kolejne operacje w dół łańcucha) otrzymają powiadomienia w wątku pobranym z observeOn Scheduler.

Oto wykres kulkowy, który pokazuje, jak te metody wpływają na miejsce uruchamiania operacji:

Marmurowy diagram, który pokazuje, jak te metody wpływają na miejsce uruchamiania operacji.

W kontekście Androida, jeśli operacja interfejsu użytkownika musi nastąpić w wyniku długiej operacji, chcielibyśmy, aby ta operacja odbyła się w wątku interfejsu użytkownika. W tym celu możemy użyć AndroidScheduler#mainThread() , jednego z Schedulerów udostępnianych w bibliotece RxAndroid.

RxJava na Androida

Teraz, gdy mamy już za sobą kilka podstawowych rzeczy, możesz się zastanawiać — jaki jest najlepszy sposób na zintegrowanie RxJava z aplikacją na Androida? Jak możesz sobie wyobrazić, istnieje wiele przypadków użycia RxJava, ale w tym przykładzie spójrzmy na jeden konkretny przypadek: użycie obiektów Observable jako części stosu sieciowego.

W tym przykładzie przyjrzymy się Retrofit, klientowi HTTP open source firmy Square, który ma wbudowane powiązania z RxJava do interakcji z API GitHub. W szczególności stworzymy prostą aplikację, która będzie prezentować wszystkie repozytoria oznaczone gwiazdką dla użytkownika o nazwie użytkownika GitHub. Jeśli chcesz iść do przodu, kod źródłowy jest dostępny tutaj.

Utwórz nowy projekt Androida

  • Zacznij od utworzenia nowego projektu Androida i nazwania go GitHubRxJava .

Zrzut ekranu: Utwórz nowy projekt Android

  • Na ekranie Docelowe urządzenia z systemem Android pozostaw wybrane telefony i tablety i ustaw minimalny poziom SDK na 17. Możesz ustawić go na niższy/wyższy poziom interfejsu API, ale w tym przykładzie wystarczy poziom interfejsu API 17.

Zrzut ekranu: ekran docelowy urządzeń z systemem Android

  • Wybierz Puste działanie w następnym monicie.

Zrzut ekranu: Dodaj aktywność do ekranu telefonu komórkowego

  • W ostatnim kroku zachowaj nazwę działania jako MainActivity i wygeneruj plik układu activity_main .

Zrzut ekranu: Dostosuj ekran aktywności

Konfiguracja projektu

Dołącz RxJava, RxAndroid i bibliotekę Retrofit w app/build.gradle . Należy zauważyć, że dołączenie RxAndroid domyślnie obejmuje również RxJava. Jednak najlepszą praktyką jest zawsze jawne dołączanie tych dwóch bibliotek, ponieważ RxAndroid nie zawsze zawiera najnowszą wersję RxJava. Wyraźne włączenie najnowszej wersji RxJava gwarantuje korzystanie z najbardziej aktualnej wersji.

 dependencies { compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0' compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'io.reactivex:rxandroid:1.2.0' compile 'io.reactivex:rxjava:1.1.8' // ...other dependencies }

Utwórz obiekt danych

Utwórz klasę obiektu danych GitHubRepo . Ta klasa hermetyzuje repozytorium w GitHub (odpowiedź sieciowa zawiera więcej danych, ale nas interesuje tylko ich podzbiór).

 public class GitHubRepo { public final int id; public final String name; public final String htmlUrl; public final String description; public final String language; public final int stargazersCount; public GitHubRepo(int id, String name, String htmlUrl, String description, String language, int stargazersCount) { this.id = id; this.name = name; this.htmlUrl = htmlUrl; this.description = description; this.language = language; this.stargazersCount = stargazersCount; } }

Modernizacja instalacji

  • Utwórz interfejs GitHubService . Przekażemy ten interfejs do Retrofit, a Retrofit utworzy implementację GitHubService .
 public interface GitHubService { @GET("users/{user}/starred") Observable<List<GitHubRepo>> getStarredRepositories(@Path("user") String userName); }
  • Utwórz klasę GitHubClient . Będzie to obiekt, z którym będziemy wchodzić w interakcje, aby wykonywać połączenia sieciowe z poziomu UI.

    • Podczas konstruowania implementacji GitHubService przez Retrofit, musimy przekazać RxJavaCallAdapterFactory jako adapter wywołania, aby wywołania sieciowe mogły zwracać obiekty Observable (przekazanie adaptera wywołania jest potrzebne dla każdego wywołania sieciowego, które zwraca wynik inny niż Call ).

    • Musimy również przekazać GsonConverterFactory , abyśmy mogli użyć Gson jako sposobu na uporządkowanie obiektów JSON do obiektów Java.

 public class GitHubClient { private static final String GITHUB_BASE_URL = "https://api.github.com/"; private static GitHubClient instance; private GitHubService gitHubService; private GitHubClient() { final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); final Retrofit retrofit = new Retrofit.Builder().baseUrl(GITHUB_BASE_URL) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); gitHubService = retrofit.create(GitHubService.class); } public static GitHubClient getInstance() { if (instance == null) { instance = new GitHubClient(); } return instance; } public Observable<List<GitHubRepo>> getStarredRepos(@NonNull String userName) { return gitHubService.getStarredRepositories(userName); } }

Układy konfiguracji

Następnie utwórz prosty interfejs użytkownika, który wyświetla pobrane repozytoria na podstawie wejściowej nazwy użytkownika GitHub. Utwórz activity_home.xml - układ dla naszej aktywności - z czymś podobnym do następującego:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ListView android: android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <EditText android: android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="@string/username"/> <Button android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/search"/> </LinearLayout> </LinearLayout>

Utwórz item_github_repo.xml - układ elementu ListView dla obiektu repozytorium GitHub - z czymś podobnym do następującego:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:andro xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="6dp"> <TextView android: android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="24sp" android:text tools:text="Cropper"/> <TextView android: android:layout_width="match_parent" android:layout_height="wrap_content" android:lines="2" android:ellipsize="end" android:textSize="16sp" android:layout_below="@+id/text_repo_name" tools:text="Android widget for cropping and rotating an image."/> <TextView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/text_repo_description" android:layout_alignParentLeft="true" android:textColor="?attr/colorPrimary" android:textSize="14sp" android:text tools:text="Language: Java"/> <TextView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/text_repo_description" android:layout_alignParentRight="true" android:textColor="?attr/colorAccent" android:textSize="14sp" android:text tools:text="Stars: 1953"/> </RelativeLayout>

Sklej wszystko razem

Utwórz ListAdapter , który jest odpowiedzialny za wiązanie obiektów GitHubRepo z elementami ListView . Proces zasadniczo obejmuje nadmuchiwanie item_github_repo.xml do View , jeśli nie jest dostarczony View z recyklingu; w przeciwnym razie View z recyklingu jest ponownie używany, aby zapobiec przepełnieniu zbyt wielu obiektów View .

 public class GitHubRepoAdapter extends BaseAdapter { private List<GitHubRepo> gitHubRepos = new ArrayList<>(); @Override public int getCount() { return gitHubRepos.size(); } @Override public GitHubRepo getItem(int position) { if (position < 0 || position >= gitHubRepos.size()) { return null; } else { return gitHubRepos.get(position); } } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { final View view = (convertView != null ? convertView : createView(parent)); final GitHubRepoViewHolder viewHolder = (GitHubRepoViewHolder) view.getTag(); viewHolder.setGitHubRepo(getItem(position)); return view; } public void setGitHubRepos(@Nullable List<GitHubRepo> repos) { if (repos == null) { return; } gitHubRepos.clear(); gitHubRepos.addAll(repos); notifyDataSetChanged(); } private View createView(ViewGroup parent) { final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); final View view = inflater.inflate(R.layout.item_github_repo, parent, false); final GitHubRepoViewHolder viewHolder = new GitHubRepoViewHolder(view); view.setTag(viewHolder); return view; } private static class GitHubRepoViewHolder { private TextView textRepoName; private TextView textRepoDescription; private TextView textLanguage; private TextView textStars; public GitHubRepoViewHolder(View view) { textRepoName = (TextView) view.findViewById(R.id.text_repo_name); textRepoDescription = (TextView) view.findViewById(R.id.text_repo_description); textLanguage = (TextView) view.findViewById(R.id.text_language); textStars = (TextView) view.findViewById(R.id.text_stars); } public void setGitHubRepo(GitHubRepo gitHubRepo) { textRepoName.setText(gitHubRepo.name); textRepoDescription.setText(gitHubRepo.description); textLanguage.setText("Language: " + gitHubRepo.language); textStars.setText("Stars: " + gitHubRepo.stargazersCount); } } }

Sklej wszystko razem w MainActivity . Zasadniczo jest to Activity , które jest wyświetlane po pierwszym uruchomieniu aplikacji. Tutaj prosimy użytkownika o podanie swojej nazwy użytkownika GitHub i na koniec wyświetlenie wszystkich repozytoriów oznaczonych gwiazdką według tej nazwy użytkownika.

 public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private GitHubRepoAdapter adapter = new GitHubRepoAdapter(); private Subscription subscription; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ListView listView = (ListView) findViewById(R.id.list_view_repos); listView.setAdapter(adapter); final EditText editTextUsername = (EditText) findViewById(R.id.edit_text_username); final Button buttonSearch = (Button) findViewById(R.id.button_search); buttonSearch.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final String username = editTextUsername.getText().toString(); if (!TextUtils.isEmpty(username)) { getStarredRepos(username); } } }); } @Override protected void onDestroy() { if (subscription != null && !subscription.isUnsubscribed()) { subscription.unsubscribe(); } super.onDestroy(); } private void getStarredRepos(String username) { subscription = GitHubClient.getInstance() .getStarredRepos(username) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<List<GitHubRepo>>() { @Override public void onCompleted() { Log.d(TAG, "In onCompleted()"); } @Override public void onError(Throwable e) { e.printStackTrace(); Log.d(TAG, "In onError()"); } @Override public void onNext(List<GitHubRepo> gitHubRepos) { Log.d(TAG, "In onNext()"); adapter.setGitHubRepos(gitHubRepos); } }); } }

Uruchom aplikację

Uruchomienie aplikacji powinno wyświetlić ekran z polem wprowadzania, w którym można wpisać nazwę użytkownika GitHub. Wyszukiwanie powinno następnie przedstawić listę wszystkich repozytoriów oznaczonych gwiazdką.

Zrzut ekranu aplikacji przedstawiający listę wszystkich repozytoriów oznaczonych gwiazdką.

Wniosek

Mam nadzieję, że posłuży to jako przydatne wprowadzenie do RxJava i przegląd jego podstawowych możliwości. W RxJava jest mnóstwo potężnych koncepcji i zachęcam do ich zbadania poprzez głębsze zagłębienie się w dobrze udokumentowaną wiki RxJava.

Zachęcamy do pozostawienia jakichkolwiek pytań lub komentarzy w polu komentarza poniżej. Możesz także śledzić mnie na Twitterze pod adresem @arriolachris, gdzie dużo ćwierkam o RxJava i wszystkich rzeczach związanych z Androidem.

Jeśli chcesz obszerny materiał edukacyjny na RxJava, możesz sprawdzić ebook, który napisałem z Angusem Huangiem na Leanpub.

Powiązane: Dziesięć funkcji Kotlin przyspieszających rozwój Androida