Практическое использование RxJava 2 в
Android
Виноградный Евгений
Android Developer
Как последовательно выполнить 2 цепи?
Условие:
Например, у нас есть Single для получения списка комментариев и Single для публикации
комментария. Необходимо обеспечить отправку комментария после загрузки списка.
Как последовательно выполнить 2 цепи?
Условие:
Например, у нас есть Single для получения списка комментариев и Single для публикации
комментария. Необходимо обеспечить отправку комментария после загрузки списка.
Single.fromCallable { dataRepository.loadComments() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(…)
Как последовательно выполнить 2 цепи?
Условие:
Например, у нас есть Single для получения списка комментариев и Single для публикации
комментария. Необходимо обеспечить отправку комментария после загрузки списка.
Single.fromCallable { dataRepository.addComment(…) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(…)
Как последовательно выполнить 2 цепи?
Решение:
Использовать Semaphore из java.util.concurrent.
val semaphore = Semaphore(1)
Single.fromCallable { dataRepository.loadComments() }
.doOnSubscribe { semaphore.acquire() }
.subscribeOn(Schedulers.io())
.doFinally { semaphore.release() }
.observeOn(AndroidSchedulers.mainThread())
.subscribe(…)
Как последовательно выполнить 2 цепи?
Решение:
Использовать Semaphore из java.util.concurrent.
val semaphore = Semaphore(1)
Single.fromCallable { dataRepository.addComment(…) }
.doOnSubscribe { semaphore.acquire() }
.subscribeOn(Schedulers.io())
.doFinally { semaphore.release() }
.observeOn(AndroidSchedulers.mainThread())
.subscribe(…)
Как последовательно выполнить 2 цепи?
Какие возможности предоставляет нам такой подход?
• Потокобезопасность для цепи.
Как последовательно выполнить 2 цепи?
Какие возможности предоставляет нам такой подход?
• Потокобезопасность для цепи.
• Потокобезопасность для отдельного участка цепи.
Как последовательно выполнить 2 цепи?
Какие возможности предоставляет нам такой подход?
• Потокобезопасность для цепи.
• Потокобезопасность для отдельного участка цепи.
• Возможность запускать одновременно определенное количество цепей.
Как последовательно выполнить 2 цепи?
Какие возможности предоставляет нам такой подход?
• Потокобезопасность для цепи.
• Потокобезопасность для отдельного участка цепи.
• Возможность запускать одновременно определенное количество цепей.
• Нет необходимости объединять все цепи в одну.
Как последовательно выполнить 2 цепи?
Обрабатываем ошибки красиво
Условие:
Необходимо выполнить запрос на сервер и:
• При отсутствии Интернет-соединения сразу выдать ошибку.
Обрабатываем ошибки красиво
Условие:
Необходимо выполнить запрос на сервер и:
• При отсутствии Интернет-соединения сразу выдать ошибку.
• При возникновении ошибки повторить запрос с задержкой в 5 секунд, не больше 3 раз.
Обрабатываем ошибки красиво
Условие:
Необходимо выполнить запрос на сервер и:
• При отсутствии Интернет-соединения сразу выдать ошибку.
• При возникновении ошибки повторить запрос с задержкой в 5 секунд, не больше 3 раз.
• Наложить ограничение в 1 минуту на выполнение всей операции.
Обрабатываем ошибки красиво
Условие:
Необходимо выполнить запрос на сервер и:
• При отсутствии Интернет-соединения сразу выдать ошибку.
• При возникновении ошибки повторить запрос с задержкой в 5 секунд, не больше 3 раз.
• Наложить ограничение в 1 минуту на выполнение всей операции.
dataRepository.requestDataFromNetwork()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(…)
Обрабатываем ошибки красиво
Решение:
dataRepository.requestDataFromNetwork()
…
.retryWhen { throwableObservable -> throwableObservable
.flatMap {
if (it is IOException) {
Observable.error(it)
} else {
Observable.just(it)
}
}
}
…
Обрабатываем ошибки красиво
Решение:
dataRepository.requestDataFromNetwork()
…
.retryWhen { throwableObservable -> throwableObservable
.flatMap {
if (it is IOException || attemptsCount.getAndIncrement() == 3) {
Observable.error(it)
} else {
Observable.just(it)
}
}
}
…
Обрабатываем ошибки красиво
Решение:
dataRepository.requestDataFromNetwork()
…
.retryWhen { throwableObservable -> throwableObservable
.flatMap {
if (it is IOException || attemptsCount.getAndIncrement() == 3) {
Observable.error(it)
} else {
Observable.just(it)
}
}
.delay(5, TimeUnit.SECONDS)
}
…
Обрабатываем ошибки красиво
Решение:
dataRepository.requestDataFromNetwork()
…
.retryWhen { throwableObservable -> throwableObservable
.flatMap {
if (it is IOException || attemptsCount.getAndIncrement() == 3) {
Observable.error(it)
} else {
Observable.just(it)
}
}
.delay(5, TimeUnit.SECONDS)
}
.timeout(1, TimeUnit.MINUTES)
…
Обрабатываем ошибки красиво
class ErrorTransformer<T> constructor(val maxAttempts…) : ObservableTransformer<T, T> {
…
override fun apply(upstream: Observable<T>) = upstream
.retryWhen { throwableObservable -> throwableObservable
.flatMap {
if (it is IOException || attemptsCount.getAndIncrement() == maxAttemptsCount) {
Observable.error(it)
} else {
Observable.just(it)
}
}
.delay(delay, TimeUnit.MILLISECONDS)
}
.timeout(timeout, TimeUnit.MILLISECONDS)
}
Обрабатываем ошибки красиво
class ErrorTransformer<T> constructor(val maxAttempts…) : ObservableTransformer<T, T> {
…
override fun apply(upstream: Observable<T>) = upstream
.retryWhen { throwableObservable -> throwableObservable
.flatMap {
Crashlytics.logException(it)
if (it is IOException || attemptsCount.getAndIncrement() == maxAttemptsCount) {
Observable.error(it)
} else {
Observable.just(it)
}
}
.delay(delay, TimeUnit.MILLISECONDS)
}
.timeout(timeout, TimeUnit.MILLISECONDS)
}
Обрабатываем ошибки красиво
Использование:
val DELAY = TimeUnit.SECONDS.toMillis(5)
val TIMEOUT = TimeUnit.MINUTES.toMillis(1)
val ATTEMPTS_COUNT = 3
dataRepository.requestDataFromNetwork()
.subscribeOn(Schedulers.io())
.compose(ErrorTransformer(TIMEOUT, DELAY, ATTEMPTS_COUNT))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(…)
Обрабатываем ошибки красиво
Использование:
val DELAY = TimeUnit.SECONDS.toMillis(5)
val TIMEOUT = TimeUnit.MINUTES.toMillis(1)
val ATTEMPTS_COUNT = 3
dataRepository.requestDataFromNetwork()
.subscribeOn(Schedulers.io())
.compose(ErrorTransformer(TIMEOUT, DELAY, ATTEMPTS_COUNT))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(…)
…
RxJavaPlugins.setErrorHandler { e -> … }
Обрабатываем ошибки красиво
Пример использования Subject
Условие:
Необходимо отслеживать подключение и отключение Интернет-соединения для выполнения
запросов на сервер.
Пример использования Subject
PublishSubject
val source = PublishSubject.create<Int>()
// Получит 1, 2, 3, 4 и onComplete
source.subscribe(getFirstObserver())
source.onNext(1)
source.onNext(2)
source.onNext(3)
// Получит 4 и onComplete для следующего наблюдателя тоже
source.subscribe(getSecondObserver());
source.onNext(4)
source.onComplete()
Пример использования Subject
RelplaySubject
val source = ReplaySubject.create<Int>()
// Он получит 1, 2, 3, 4
source.subscribe(getFirstObserver())
source.onNext(1)
source.onNext(2)
source.onNext(3)
source.onNext(4)
source.onComplete()
// Он также получит 1, 2, 3, 4 так как он использует Replay Subject
source.subscribe(getSecondObserver())
Пример использования Subject
BehaviorSubject
val source = BehaviorSubject.create<Int>()
// Получит 1, 2, 3, 4 and onComplete
source.subscribe(getFirstObserver())
source.onNext(1)
source.onNext(2)
source.onNext(3)
// Получит 3(последний элемент) и 4(последующие элементы) и onComplete
source.subscribe(getSecondObserver())
source.onNext(4)
source.onComplete()
Пример использования Subject
AsyncSubject
val source = AsyncSubject.create<Int>()
// Получит только 4 и onComplete
source.subscribe(getFirstObserver())
source.onNext(1)
source.onNext(2)
source.onNext(3)
// Тоже получит только 4 и onComplete
source.subscribe(getSecondObserver())
source.onNext(4)
source.onComplete()
Пример использования Subject
Решение:
class ConnectivityReceiver(val connectivitySubject: PublishSubject<Boolean>… {
override fun onReceive(context: Context, arg1: Intent) {
connectivitySubject.onNext(isConnected())
}
private fun isConnected(): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE)
val activeNetwork = connectivityManager.activeNetworkInfo
return activeNetwork != null && activeNetwork.isConnected
}
}
Пример использования Subject
Решение:
…
override fun onStart() {
registerReceiver(connectivityReceiver,
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
super.onStart()
}
override fun onStop() {
unregisterReceiver(connectivityReceiver)
super.onStop()
}
…
Пример использования Subject
Другие примеры использования:
• Автоматическое отслеживание просмотренных элементов списка.
• Обработка нажатий (кликов) на элементы списка.
• Отображение ошибок.
• Обновление элементов.
… и многое другое.
Пример использования Subject
Анимирование View
Условие:
Необходимо реализовать последовательное появление нескольких View.
Анимирование View
Условие:
Необходимо реализовать последовательное появление нескольких View.
Анимирование View
Решение:
Будем использовать ViewPropertyAnimator.
ViewCompat.animate(view)
.setDuration(duration)
.alpha(1f)
Анимирование View
Решение:
fun fadeIn(view: View, duration: Long) = Completable.create {
ViewCompat.animate(view)
.setDuration(duration)
.alpha(1f)
.withEndAction {
it.onComplete()
}
}
Анимирование View
Использование:
val durationMs = 1000L
fadeIn(button1, durationMs)
.andThen(fadeIn(button2, durationMs))
.andThen(fadeIn(button3, durationMs))
.andThen(fadeIn(button4, durationMs))
.subscribe()
Анимирование View
Условие:
Необходимо реализовать одновременное появление нескольких View, но продолжительность
анимации одной из них должна быть на 1 секунду больше.
Анимирование View
Условие:
Необходимо реализовать одновременное появление нескольких View, но продолжительность
анимации одной из них должна быть на 1 секунду больше.
Анимирование View
Более красивое решение:
fun View.fadeIn(duration: Long = 1000L) = Completable.create {
ViewCompat.animate(this)
.setDuration(duration)
.alpha(1f)
.withEndAction {
it.onComplete()
}
}
Анимирование View
Использование:
button1.fadeIn()
.mergeWith(button2.fadeIn(2000))
.mergeWith(button3.fadeIn())
.mergeWith(button4.fadeIn())
.subscribe()
Анимирование View
Реактивный поиск
Условие:
Необходимо реализовать поиск и при изменении строки запрашивать данные с сервера.
Должны соблюдаться условия:
• Данные с сервера должны запрашиваться не чаще, чем 1 раз в 500 миллисекунд.
Реактивный поиск
Условие:
Необходимо реализовать поиск и при изменении строки запрашивать данные с сервера.
Должны соблюдаться условия:
• Данные с сервера должны запрашиваться не чаще, чем 1 раз в 500 миллисекунд.
• Пустые строки должны игнорироваться.
Реактивный поиск
Условие:
Необходимо реализовать поиск и при изменении строки запрашивать данные с сервера.
Должны соблюдаться условия:
• Данные с сервера должны запрашиваться не чаще, чем 1 раз в 500 миллисекунд.
• Пустые строки должны игнорироваться.
• Если строка была отредактирована, но ее содержимое осталось прежним, то запрос
отправляться не должен.
Реактивный поиск
Условие:
Необходимо реализовать поиск и при изменении строки запрашивать данные с сервера.
Должны соблюдаться условия:
• Данные с сервера должны запрашиваться не чаще, чем 1 раз в 500 миллисекунд.
• Пустые строки должны игнорироваться.
• Если строка была отредактирована, но ее содержимое осталось прежним, то запрос
отправляться не должен.
• Должен отменяться последний запрос, если строка изменилась.
Реактивный поиск
Решение:
editText.textChanges()
.subscribe(…)
Реактивный поиск
Решение:
editText.textChanges()
.delay(500, TimeUnit.MILLISECONDS)
.subscribe(…)
Реактивный поиск
Решение:
editText.textChanges()
.delay(500, TimeUnit.MILLISECONDS)
.filter { !it.isEmpty() }
.subscribe(…)
Реактивный поиск
Решение:
editText.textChanges()
.delay(500, TimeUnit.MILLISECONDS)
.filter { !it.isEmpty() }
.map { it.toString() }
.distinctUntilChanged()
.subscribe(…)
Реактивный поиск
Решение:
editText.textChanges()
.delay(500, TimeUnit.MILLISECONDS)
.filter { !it.isEmpty() }
.map { it.toString() }
.distinctUntilChanged()
.switchMap { dataRepository.getResultsByQuery(it) }
.subscribe(…)
Реактивный поиск
Решение:
editText.textChanges()
.debounce(500, TimeUnit.MILLISECONDS) // Более корректное использование
.filter { !it.isEmpty() }
.map { it.toString() }
.distinctUntilChanged()
.switchMap { dataRepository.getResultsByQuery(it) }
.subscribe(…)
Реактивный поиск
Кэширование данных
Условие:
Необходимо организовать кэширование данных в памяти, на диске и при отсутствии данных
запросить их с сервера.
Кэширование данных
Условие:
Необходимо организовать кэширование данных в памяти, на диске и при отсутствии данных
запросить их с сервера.
Кэширование данных
Решение:
…
fun getDataFromMemory() = memoryDataSource.getData()
fun getDataFromDisk() = diskDataSource.getData().doOnNext { data ->
memoryDataSource.cacheInMemory(data)
}
fun getDataFromNetwork() = networkDataSource.getData().doOnNext { data ->
diskDataSource.saveToDisk(data)
memoryDataSource.cacheInMemory(data)
}
…
Кэширование данных
Использование:
val memory = dataSource.getDataFromMemory()
val disk = dataSource.getDataFromDisk()
val network = dataSource.getDataFromNetwork()
Observable.concat<Data>(memory, disk, network)
.subscribeOn(Schedulers.io())
.firstElement()
.toObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(…)
Кэширование данных
Спасибо за внимание!

Practical usage of RxJava 2