Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

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

1 748 vues

Publié le

2018년 4월 22일 드로이드 나이츠 2108에서 발표한 RxJava 세션 프리젠테이션입니다.

Publié dans : Mobile
  • Soyez le premier à commenter

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

  1. 1. 다시 보는 RxJava 네이버 노재춘
  2. 2. 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() ); 무한 이벤트에서 문제 발생 가능
  3. 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. 4. 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
  5. 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. 6. 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.
  7. 7. 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.
  8. 8. 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]
  9. 9. 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) }
  10. 10. 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 넣 을 수 없음
  11. 11. Rx Contract #3-2 obs.subscribe(value -> System.out.println( Thread.currentThread().getName() + ":" + value), System.err::println, () -> System.out.println("onComplete")); onComplete 불 리지 않음
  12. 12. 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) 모두 필요
  13. 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. 14. 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());
  15. 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. 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. 17. flatMap thread #1-2
  18. 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. 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. 20. merge thread #1-2
  21. 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. 22. zip thread #1-2
  23. 23. RetryWhen #1 - SocketTimeout에 재시도(3번 재시도) - 401 status code에는 인증 API 호출
  24. 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. 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. 26. 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); }
  27. 27. 예외 처리 #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을 상수로 해도 큰 차이가 없음 } }
  28. 28. 예외 처리 #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"); } }
  29. 29. 예외 처리 #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);
  30. 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. 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. 32. Q & A

×