다시 보는 RxJava
네이버 노재춘
Rx Contract #1-1
no more messages should arrive after an onError or
onComplete message
RxView.clicks(button)
.flatMap(ignored -> getBestSeller()
.subscribeOn(Schedulers.io()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bookTitle -> title.setText(bookTitle),
e -> Toast.makeText(this, "문제 발생",
Toast.LENGTH_LONG).show()
);
무한 이벤트에서 문제 발생 가능
Rx Contract #1-2
RxView.clicks(button)
.flatMap(ignored -> getBestSeller()
.subscribeOn(Schedulers.io())
.map(title -> new ViewState.Result(title)) // (1)
.cast(ViewState.class) // (2)
.onErrorReturn(e -> new ViewState.Error(e))) // (3)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(viewState -> {
if (viewState instanceof ViewState.Result) { // (4)
title.setText(((ViewState.Result) viewState).title);
} else if (viewState instanceof ViewState.Error) { // (5)
Toast.makeText(this, "문제 발생", Toast.LENGTH_LONG).show();
}
});
Rx Contract #1-3
interface ViewState {
class Error implements ViewState {
Throwable e;
Error(Throwable e) {
this.e = e;
}
}
class Result implements ViewState {
String title;
Result(String title) {
this.title = title;
}
}
}
http://hannesdorfmann.com/android/
mosby3-mvi-1
visual bugs such as displaying a loading
indicator (“loading state”) and error indicator
(“error state”) at the same time => exactly
one output
Rx Contract #2-1
스트림에 널(null) 허용 안됨
@Test(expected = NullPointerException.class)
public void nullEvent() {
Observable.just("Hello", null, "RxJava")
.subscribe(System.out::println,
System.err::println);
}
Observable 생성
에서 에러 발생
Rx Contract #2-2
@Test
public void nullEventOnCreate() {
Observable.create(emitter -> {
emitter.onNext("Hello");
emitter.onNext(null);
emitter.onNext("RxJava");
}).subscribe(System.out::println,
System.err::println);
}
java.lang.NullPointerException:
onNext called with null. Null
values are generally not
allowed in 2.x operators and
sources.
Rx Contract #2-3
@Test
public void nullEventPossible() {
Observable.just(1, 2, -1, 1, 2)
.map(dollar -> getCurrentPrice(dollar))
.subscribe(System.out::println,
System.err::println);
}
private String getCurrentPrice(int dollar) {
if (dollar < 0) {
return null;
}
return (dollar * 1000) + " won";
}
1000 won
2000 won
java.lang.NullPointerException:
The mapper function returned
a null value.
Rx Contract #2-4
@Test
public void nullEventPossible3() {
Observable.just(1, 2, -1, 1, 2)
.map(dollar -> getCurrentPrice3(dollar))
.onErrorReturnItem("0 won")
.subscribe(System.out::println,
System.err::println,
() -> System.out.println("onComplete"));
}
private String getCurrentPrice3(int dollar) {
if (dollar < 0) {
throw new IllegalArgumentException("dollar should be bigger than 0");
}
return (dollar * 1000) + " won";
} [1000 won, 2000 won, 0 won, onComplete]
Rx Contract #2-5
@Test
public void nullEventPossible4() {
Observable.just(1, 2, -1, 1, 2)
.flatMap(dollar -> getCurrentPrice4(dollar)
.onErrorReturnItem("0 won")) // (1)
.subscribe(System.out::println,
System.err::println);
}
private Observable getCurrentPrice4(int dollar) {
if (dollar < 0) {
return Observable.error(
new IllegalArgumentException("dollar should be bigger than 0")); // (2)
}
return Observable.just((dollar * 1000) + " won"); // (3)
}
Rx Contract #3-1
Assume observer instances are called in a serialized fashion
Observable obs = Observable.create(emitter -> {
new Thread(() -> {
emitter.onNext(1);
emitter.onNext(3);
. . . .
emitter.onNext(9);
// emitter.onComplete(); // (1)
}).start();
new Thread(() -> {
emitter.onNext(2);
emitter.onNext(4);
. . . .
emitter.onNext(10);
// emitter.onComplete(); // (2)
}).start();
});
onComplete 넣
을 수 없음
Rx Contract #3-2
obs.subscribe(value -> System.out.println(
Thread.currentThread().getName() + ":" + value),
System.err::println,
() -> System.out.println("onComplete"));
onComplete 불
리지 않음
Rx Contract #3-3
Observable obs1 = Observable.create(emitter -> {
new Thread(() -> {
emitter.onNext(1);
. . . .
emitter.onNext(9);
emitter.onComplete(); // (1)
}).start();
});
Observable obs2 = Observable.create(emitter -> {
new Thread(() -> {
emitter.onNext(2);
. . . .
emitter.onNext(10);
emitter.onComplete(); // (2)
}).start();
});
(1), (2)
모두 필요
Rx Contract #3-4
Observable.merge(obs1, obs2).subscribe(value ->
System.out.println(Thread.currentThread().getName() + ":" + value),
System.err::println,
() -> System.out.println("onComplete")
);
onComplete 정
상 호출
Rx Contract #3-5
Observable obs1 = Observable.create(emitter -> {
emitter.onNext(1);
emitter.onNext(3);
. . . .
emitter.onNext(9);
emitter.onComplete();
}).subscribeOn(Schedulers.computation());
Observable obs2 = Observable.create(emitter -> {
emitter.onNext(2);
emitter.onNext(4);
. . . .
emitter.onNext(10);
emitter.onComplete();
}).subscribeOn(Schedulers.computation());
Rx Contract #3-6
Observable.merge(obs1, obs2).subscribe(value ->
System.out.println(
Thread.currentThread().getName() + ":" + value),
System.err::println,
() -> System.out.println("onComplete")
);
flatMap thread #1-1
repository.getBookCategories().toObservable()
.flatMapIterable(categories -> categories)
.flatMapSingle(category -> repository.getCategoryBooks(category.categoryId))
.subscribeOn(Schedulers.io())
.collect(ArrayList::new, ArrayList::addAll)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> title.setText(books.toString()
repository.getBookCategories().toObservable()
.subscribeOn(Schedulers.io())
.flatMapIterable(categories -> categories)
.flatMapSingle(category ->
repository.getCategoryBooks(category.categoryId)
.subscribeOn(Schedulers.io()))
.collect(ArrayList::new, ArrayList::addAll)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> title.setText(books.toString()));
flatMap thread #1-2
flatMap thread #2 단일 이벤트 연결
repository.getRegion(location)
.map(region -> region.areaCode)
.flatMap(repository::getWeather)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(weather -> title.setText(weather.toString()));
repository.getRegion(location)
.subscribeOn(Schedulers.io())
.map(region -> region.areaCode)
.flatMap(areaCode -> repository.getWeather(areaCode)
.subscribeOn(Schedulers.io()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(weather -> title.setText(weather.toString()));
merge thread #1-1
Single.merge(repository.getBestSeller(),
repository.getRecommendBooks(),
repository.getCategoryBooks(7))
.subscribeOn(Schedulers.io())
.collect(ArrayList::new, ArrayList::addAll)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> title.setText(books.toString()));
Single.merge(repository.getBestSeller().subscribeOn(Schedulers.io()),
repository.getRecommendBooks().subscribeOn(Schedulers.io()),
repository.getCategoryBooks(7).subscribeOn(Schedulers.io()))
.collect(ArrayList::new, ArrayList::addAll)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> title.setText(books.toString()));
merge thread #1-2
zip thread #1-1
Single.zip(repository.getWeather(regionCode),
repository.getWeatherDetail(regionCode),
Pair::new)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(pair -> title.setText(pair.first + ", " + pair.second));
Single.zip(repository.getWeather(regionCode).subscribeOn(Schedulers.io()),
repository.getWeatherDetail(regionCode).subscribeOn(Schedulers.io()),
Pair::new)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(pair -> title.setText(pair.first + ", " + pair.second));
zip thread #1-2
RetryWhen #1
- SocketTimeout에 재시도(3번 재시도)
- 401 status code에는 인증 API 호출
RetryWhen #2
public Single<Profile> getProfile() {
return profileRepository.getProfile().retryWhen(errors ->
errors.zipWith(Flowable.range(0, LIMIT),
this::handleRetryAttempt).flatMap(x -> x));
}
private Flowable<Long> handleRetryAttempt(Throwable throwable, int attempt) {
if (throwable instanceof SocketTimeoutException) {
switch (attempt) {
case LIMIT:
return Flowable.error(throwable);
default:
return Flowable.timer(attempt * 3, TimeUnit.SECONDS);
}
} else {else if (throwable instanceof HttpException) {
// 다음 슬라이드
}
OkHttpClient Interceptor도 가능?
원래 흐름으로 돌아올 방법 없음
RetryWhen #3
private Flowable<Long> handleRetryAttempt(Throwable throwable, int attempt) {
if (throwable instanceof SocketTimeoutException) {
...
} else if (throwable instanceof HttpException) {
int statusCode = ((HttpException) throwable).code();
if (statusCode == 401) {
return profileRepository.refresh().retry((refreshAttempt, e) ->
refreshAttempt < LIMIT
&& e instanceof SocketTimeoutException)
.map(x -> 1L).toFlowable();
}
}
return Flowable.error(throwable);
}
RetryWhen #4
public class RetryTransformer<T> implements SingleTransformer<T, T> {
. . . .
@Override
public SingleSource apply(Single<T> upstream) {
return upstream.retryWhen(errors ->
errors.zipWith(Flowable.range(0, LIMIT),
this::handleRetryAttempt).flatMap(x -> x));
}
}
public Single<Profile> getProfile() {
return profileRepository.getProfile().compose(retryTransformer);
}
예외 처리 #1
public long getTimeInMillis1(String input) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
return sdf.parse(input).getTime();
}
public long getTimeInMillis2(String input) {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
try {
return sdf.parse(input).getTime();
} catch (ParseException e) {
e.printStackTrace();
return -1L; // -1을 상수로 해도 큰 차이가 없음
}
}
예외 처리 #2
public long getTimeInMillis3(String input) {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
try {
return sdf.parse(input).getTime();
} catch (ParseException e) {
throw new IllegalArgumentException("Illegal input pattern");
}
}
예외 처리 #3
public Single<Long> getTimeInMillis(String input) {
return Single.fromCallable(() -> {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
return sdf.parse(input).getTime();
});
}
String input = "2017-xx-30 02:20:20";
DateFormatter formatter = new DateFormatter();
long start = formatter.getTimeInMillis(input)
.onErrorReturnItem(0L)
.blockingGet();
long end = formatter.getTimeInMillis(input)
.onErrorReturnItem(new Date().getTime())
.blockingGet();
System.out.println("start=" + start + ", end=" + end);
flatMap BiFunction
Observable.range(2, 8)
.flatMap(row -> Observable.range(1, 9)
.map(col -> String.format("%d x %d = %d",
row, col, row * col)))
.subscribe(System.out::println);
Observable.range(2, 8)
.flatMap(row -> Observable.range(1, 9),
(row, col) -> String.format("%d x %d = %d",
row, col, row * col))
.subscribe(System.out::println);
collect
Observable<String> obs = Observable.fromIterable(searchOptions)
.filter(SearchOption::isInUse)
.map(SearchOption::getName);
obs.subscribe(name -> {
applySearchOptions.add(name);
}, e -> {
}, () -> {
showData(applySearchOptions);
});
Single<ArrayList<String>> obs = Observable.fromIterable(searchOptions)
.filter(SearchOption::isInUse)
.map(SearchOption::getName)
.collect(ArrayList<String>::new, List::add);
obs.subscribe(this::showData);
Q & A
드로이드 나이츠 2018: RxJava 적용 팁 및 트러블 슈팅

드로이드 나이츠 2018: RxJava 적용 팁 및 트러블 슈팅

  • 1.
  • 2.
    Rx Contract #1-1 nomore messages should arrive after an onError or onComplete message RxView.clicks(button) .flatMap(ignored -> getBestSeller() .subscribeOn(Schedulers.io())) .observeOn(AndroidSchedulers.mainThread()) .subscribe(bookTitle -> title.setText(bookTitle), e -> Toast.makeText(this, "문제 발생", Toast.LENGTH_LONG).show() ); 무한 이벤트에서 문제 발생 가능
  • 3.
    Rx Contract #1-2 RxView.clicks(button) .flatMap(ignored-> getBestSeller() .subscribeOn(Schedulers.io()) .map(title -> new ViewState.Result(title)) // (1) .cast(ViewState.class) // (2) .onErrorReturn(e -> new ViewState.Error(e))) // (3) .observeOn(AndroidSchedulers.mainThread()) .subscribe(viewState -> { if (viewState instanceof ViewState.Result) { // (4) title.setText(((ViewState.Result) viewState).title); } else if (viewState instanceof ViewState.Error) { // (5) Toast.makeText(this, "문제 발생", Toast.LENGTH_LONG).show(); } });
  • 4.
    Rx Contract #1-3 interfaceViewState { class Error implements ViewState { Throwable e; Error(Throwable e) { this.e = e; } } class Result implements ViewState { String title; Result(String title) { this.title = title; } } } http://hannesdorfmann.com/android/ mosby3-mvi-1 visual bugs such as displaying a loading indicator (“loading state”) and error indicator (“error state”) at the same time => exactly one output
  • 5.
    Rx Contract #2-1 스트림에널(null) 허용 안됨 @Test(expected = NullPointerException.class) public void nullEvent() { Observable.just("Hello", null, "RxJava") .subscribe(System.out::println, System.err::println); } Observable 생성 에서 에러 발생
  • 6.
    Rx Contract #2-2 @Test publicvoid nullEventOnCreate() { Observable.create(emitter -> { emitter.onNext("Hello"); emitter.onNext(null); emitter.onNext("RxJava"); }).subscribe(System.out::println, System.err::println); } java.lang.NullPointerException: onNext called with null. Null values are generally not allowed in 2.x operators and sources.
  • 7.
    Rx Contract #2-3 @Test publicvoid nullEventPossible() { Observable.just(1, 2, -1, 1, 2) .map(dollar -> getCurrentPrice(dollar)) .subscribe(System.out::println, System.err::println); } private String getCurrentPrice(int dollar) { if (dollar < 0) { return null; } return (dollar * 1000) + " won"; } 1000 won 2000 won java.lang.NullPointerException: The mapper function returned a null value.
  • 8.
    Rx Contract #2-4 @Test publicvoid nullEventPossible3() { Observable.just(1, 2, -1, 1, 2) .map(dollar -> getCurrentPrice3(dollar)) .onErrorReturnItem("0 won") .subscribe(System.out::println, System.err::println, () -> System.out.println("onComplete")); } private String getCurrentPrice3(int dollar) { if (dollar < 0) { throw new IllegalArgumentException("dollar should be bigger than 0"); } return (dollar * 1000) + " won"; } [1000 won, 2000 won, 0 won, onComplete]
  • 9.
    Rx Contract #2-5 @Test publicvoid nullEventPossible4() { Observable.just(1, 2, -1, 1, 2) .flatMap(dollar -> getCurrentPrice4(dollar) .onErrorReturnItem("0 won")) // (1) .subscribe(System.out::println, System.err::println); } private Observable getCurrentPrice4(int dollar) { if (dollar < 0) { return Observable.error( new IllegalArgumentException("dollar should be bigger than 0")); // (2) } return Observable.just((dollar * 1000) + " won"); // (3) }
  • 10.
    Rx Contract #3-1 Assumeobserver instances are called in a serialized fashion Observable obs = Observable.create(emitter -> { new Thread(() -> { emitter.onNext(1); emitter.onNext(3); . . . . emitter.onNext(9); // emitter.onComplete(); // (1) }).start(); new Thread(() -> { emitter.onNext(2); emitter.onNext(4); . . . . emitter.onNext(10); // emitter.onComplete(); // (2) }).start(); }); onComplete 넣 을 수 없음
  • 11.
    Rx Contract #3-2 obs.subscribe(value-> System.out.println( Thread.currentThread().getName() + ":" + value), System.err::println, () -> System.out.println("onComplete")); onComplete 불 리지 않음
  • 12.
    Rx Contract #3-3 Observableobs1 = Observable.create(emitter -> { new Thread(() -> { emitter.onNext(1); . . . . emitter.onNext(9); emitter.onComplete(); // (1) }).start(); }); Observable obs2 = Observable.create(emitter -> { new Thread(() -> { emitter.onNext(2); . . . . emitter.onNext(10); emitter.onComplete(); // (2) }).start(); }); (1), (2) 모두 필요
  • 13.
    Rx Contract #3-4 Observable.merge(obs1,obs2).subscribe(value -> System.out.println(Thread.currentThread().getName() + ":" + value), System.err::println, () -> System.out.println("onComplete") ); onComplete 정 상 호출
  • 14.
    Rx Contract #3-5 Observableobs1 = Observable.create(emitter -> { emitter.onNext(1); emitter.onNext(3); . . . . emitter.onNext(9); emitter.onComplete(); }).subscribeOn(Schedulers.computation()); Observable obs2 = Observable.create(emitter -> { emitter.onNext(2); emitter.onNext(4); . . . . emitter.onNext(10); emitter.onComplete(); }).subscribeOn(Schedulers.computation());
  • 15.
    Rx Contract #3-6 Observable.merge(obs1,obs2).subscribe(value -> System.out.println( Thread.currentThread().getName() + ":" + value), System.err::println, () -> System.out.println("onComplete") );
  • 16.
    flatMap thread #1-1 repository.getBookCategories().toObservable() .flatMapIterable(categories-> categories) .flatMapSingle(category -> repository.getCategoryBooks(category.categoryId)) .subscribeOn(Schedulers.io()) .collect(ArrayList::new, ArrayList::addAll) .observeOn(AndroidSchedulers.mainThread()) .subscribe(books -> title.setText(books.toString() repository.getBookCategories().toObservable() .subscribeOn(Schedulers.io()) .flatMapIterable(categories -> categories) .flatMapSingle(category -> repository.getCategoryBooks(category.categoryId) .subscribeOn(Schedulers.io())) .collect(ArrayList::new, ArrayList::addAll) .observeOn(AndroidSchedulers.mainThread()) .subscribe(books -> title.setText(books.toString()));
  • 17.
  • 18.
    flatMap thread #2단일 이벤트 연결 repository.getRegion(location) .map(region -> region.areaCode) .flatMap(repository::getWeather) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(weather -> title.setText(weather.toString())); repository.getRegion(location) .subscribeOn(Schedulers.io()) .map(region -> region.areaCode) .flatMap(areaCode -> repository.getWeather(areaCode) .subscribeOn(Schedulers.io())) .observeOn(AndroidSchedulers.mainThread()) .subscribe(weather -> title.setText(weather.toString()));
  • 19.
    merge thread #1-1 Single.merge(repository.getBestSeller(), repository.getRecommendBooks(), repository.getCategoryBooks(7)) .subscribeOn(Schedulers.io()) .collect(ArrayList::new,ArrayList::addAll) .observeOn(AndroidSchedulers.mainThread()) .subscribe(books -> title.setText(books.toString())); Single.merge(repository.getBestSeller().subscribeOn(Schedulers.io()), repository.getRecommendBooks().subscribeOn(Schedulers.io()), repository.getCategoryBooks(7).subscribeOn(Schedulers.io())) .collect(ArrayList::new, ArrayList::addAll) .observeOn(AndroidSchedulers.mainThread()) .subscribe(books -> title.setText(books.toString()));
  • 20.
  • 21.
    zip thread #1-1 Single.zip(repository.getWeather(regionCode), repository.getWeatherDetail(regionCode), Pair::new) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(pair-> title.setText(pair.first + ", " + pair.second)); Single.zip(repository.getWeather(regionCode).subscribeOn(Schedulers.io()), repository.getWeatherDetail(regionCode).subscribeOn(Schedulers.io()), Pair::new) .observeOn(AndroidSchedulers.mainThread()) .subscribe(pair -> title.setText(pair.first + ", " + pair.second));
  • 22.
  • 23.
    RetryWhen #1 - SocketTimeout에재시도(3번 재시도) - 401 status code에는 인증 API 호출
  • 24.
    RetryWhen #2 public Single<Profile>getProfile() { return profileRepository.getProfile().retryWhen(errors -> errors.zipWith(Flowable.range(0, LIMIT), this::handleRetryAttempt).flatMap(x -> x)); } private Flowable<Long> handleRetryAttempt(Throwable throwable, int attempt) { if (throwable instanceof SocketTimeoutException) { switch (attempt) { case LIMIT: return Flowable.error(throwable); default: return Flowable.timer(attempt * 3, TimeUnit.SECONDS); } } else {else if (throwable instanceof HttpException) { // 다음 슬라이드 } OkHttpClient Interceptor도 가능? 원래 흐름으로 돌아올 방법 없음
  • 25.
    RetryWhen #3 private Flowable<Long>handleRetryAttempt(Throwable throwable, int attempt) { if (throwable instanceof SocketTimeoutException) { ... } else if (throwable instanceof HttpException) { int statusCode = ((HttpException) throwable).code(); if (statusCode == 401) { return profileRepository.refresh().retry((refreshAttempt, e) -> refreshAttempt < LIMIT && e instanceof SocketTimeoutException) .map(x -> 1L).toFlowable(); } } return Flowable.error(throwable); }
  • 26.
    RetryWhen #4 public classRetryTransformer<T> implements SingleTransformer<T, T> { . . . . @Override public SingleSource apply(Single<T> upstream) { return upstream.retryWhen(errors -> errors.zipWith(Flowable.range(0, LIMIT), this::handleRetryAttempt).flatMap(x -> x)); } } public Single<Profile> getProfile() { return profileRepository.getProfile().compose(retryTransformer); }
  • 27.
    예외 처리 #1 publiclong getTimeInMillis1(String input) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); return sdf.parse(input).getTime(); } public long getTimeInMillis2(String input) { SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); try { return sdf.parse(input).getTime(); } catch (ParseException e) { e.printStackTrace(); return -1L; // -1을 상수로 해도 큰 차이가 없음 } }
  • 28.
    예외 처리 #2 publiclong getTimeInMillis3(String input) { SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); try { return sdf.parse(input).getTime(); } catch (ParseException e) { throw new IllegalArgumentException("Illegal input pattern"); } }
  • 29.
    예외 처리 #3 publicSingle<Long> getTimeInMillis(String input) { return Single.fromCallable(() -> { SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); return sdf.parse(input).getTime(); }); } String input = "2017-xx-30 02:20:20"; DateFormatter formatter = new DateFormatter(); long start = formatter.getTimeInMillis(input) .onErrorReturnItem(0L) .blockingGet(); long end = formatter.getTimeInMillis(input) .onErrorReturnItem(new Date().getTime()) .blockingGet(); System.out.println("start=" + start + ", end=" + end);
  • 30.
    flatMap BiFunction Observable.range(2, 8) .flatMap(row-> Observable.range(1, 9) .map(col -> String.format("%d x %d = %d", row, col, row * col))) .subscribe(System.out::println); Observable.range(2, 8) .flatMap(row -> Observable.range(1, 9), (row, col) -> String.format("%d x %d = %d", row, col, row * col)) .subscribe(System.out::println);
  • 31.
    collect Observable<String> obs =Observable.fromIterable(searchOptions) .filter(SearchOption::isInUse) .map(SearchOption::getName); obs.subscribe(name -> { applySearchOptions.add(name); }, e -> { }, () -> { showData(applySearchOptions); }); Single<ArrayList<String>> obs = Observable.fromIterable(searchOptions) .filter(SearchOption::isInUse) .map(SearchOption::getName) .collect(ArrayList<String>::new, List::add); obs.subscribe(this::showData);
  • 32.