Upload
2-
View
143
Download
1
Embed Size (px)
Citation preview
Реактивный двигатель вашего Android приложения
Матвей Мальков
Обо мне
3Обо мне
О чем доклад
5
• практика
6О чем доклад
• практика
• проблемы
7О чем доклад
• практика
• проблемы
• решения
8О чем доклад
• практика
• проблемы
• решения
• android + RxJava
9О чем доклад
Сейчас
11
User Interface
12
User Interface
TextView
EditText
EditText
Button Button
13
User Interface
TextView
EditText
EditText
Button Button
State
14
Data
15
Data
API
CacheDevice
DB
16
Data
State
API
CacheDevice
DB
17
Состояние системы
• мутабельно
18Состояние системы
• мутабельно
• обновляется через callback
19Состояние системы
• мутабельно
• обновляется через callback
• плохо композируется
20Состояние системы
• мутабельно
• обновляется через callback
• плохо композируется
• явное
21Состояние системы
Мутабельность
23
User user = new User("Jake Mobius"); /** * 100 качественных строчек кода */final String name = user.getName(); //этот assert легко может упастьassert("Jake Mobius".equals(name));
24// какой-то тред User user = new User("Jake Mobius"); fetchInfo(user); final String name = user.getName(); //этот assert легко может упастьassert("Jake Mobius".equals(name));
25// какой-то тред User user = new User("Jake Mobius"); fetchInfo(user); final String name = user.getName(); //этот assert легко может упастьassert("Jake Mobius".equals(name));
// в это же время// другой какой-то тредuser.setName("Jake Popik");
Обновляемость через callback
27
28
29nameEditText.addTextChangedListener(new Watcher() { @Override public void afterTextChanged(Editable editable) { requestDuplicates(editable.toString(), new MyCallback() { @Override public void onResponse(Response response){ checkResponseForDuplicates(response, new MyCallback() { @Override public void onResponse(Response response) { setHasDuplicates(calcDuplicates(response)); } }); } }); } });
30nameEditText.addTextChangedListener(new Watcher() { @Override public void afterTextChanged(Editable editable) { requestDuplicates(editable.toString(), new MyCallback() { @Override public void onResponse(Response response){ checkResponseForDuplicates(response, new MyCallback() { @Override public void onResponse(Response response) { setHasDuplicates(calcDuplicates(response)); } }); } }); } });
31nameEditText.addTextChangedListener(new Watcher() { @Override public void afterTextChanged(Editable editable) { requestDuplicates(editable.toString(), new MyCallback() { @Override public void onResponse(Response response){ checkResponseForDuplicates(response, new MyCallback() { @Override public void onResponse(Response response) { setHasDuplicates(calcDuplicates(response)); } }); } }); } });
32nameEditText.addTextChangedListener(new Watcher() { @Override public void afterTextChanged(Editable editable) { requestDuplicates(editable.toString(), new MyCallback() { @Override public void onResponse(Response response){ checkResponseForDuplicates(response, new MyCallback() { @Override public void onResponse(Response response) { setHasDuplicates(calcDuplicates(response)); } }); } }); } });
Плохая композируемость
34
35
passEditText.addTextChangedListener(new Watcher() { @Override public void afterTextChanged(Editable editable) { lastPassText = editable.toString(); checkEqualityToEnableButton(); } }); confirmEditText.addTextChangedListener(new Watcher() { @Override public void afterTextChanged(Editable editable) { lastConfirmText = editable.toString(); checkEqualityToEnableButton(); } });
36
passEditText.addTextChangedListener(new Watcher() { @Override public void afterTextChanged(Editable editable) { lastPassText = editable.toString(); checkEqualityToEnableButton(); } }); confirmEditText.addTextChangedListener(new Watcher() { @Override public void afterTextChanged(Editable editable) { lastConfirmText = editable.toString(); checkEqualityToEnableButton(); } });
37
38nameEditText.addTextChangedListener(new Watcher() { @Override public void afterTextChanged(Editable editable) { lastIsNameEmpty = editable.toString().length() == 0; requestDuplicates(editable.toString(), new MyCallback() { @Override public void onResponse(Response response){ checkResponseForDuplicates(response, new MyCallback() { @Override public void onResponse(Response response) { lastIsHasDuplicates = calcDuplicates(response); setHasDuplicates(lastIsHasDuplicates); checkEqualityToEnableButton(); } }); } }); } });
39nameEditText.addTextChangedListener(new Watcher() { @Override public void afterTextChanged(Editable editable) { lastIsNameEmpty = editable.toString().length() == 0; requestDuplicates(editable.toString(), new MyCallback() { @Override public void onResponse(Response response){ checkResponseForDuplicates(response, new MyCallback() { @Override public void onResponse(Response response) { lastIsHasDuplicates = calcDuplicates(response); setHasDuplicates(lastIsHasDuplicates); checkEqualityToEnableButton(); } }); } }); } });
40nameEditText.addTextChangedListener(new Watcher() { @Override public void afterTextChanged(Editable editable) { lastIsNameEmpty = editable.toString().length() == 0; requestDuplicates(editable.toString(), new MyCallback() { @Override public void onResponse(Response response){ checkResponseForDuplicates(response, new MyCallback() { @Override public void onResponse(Response response) { lastIsHasDuplicates = calcDuplicates(response); setHasDuplicates(lastIsHasDuplicates); checkEqualityToEnableButton(); } }); } }); } });
41nameEditText.addTextChangedListener(new Watcher() { @Override public void afterTextChanged(Editable editable) { lastIsNameEmpty = editable.toString().length() == 0; requestDuplicates(editable.toString(), new MyCallback() { @Override public void onResponse(Response response){ checkResponseForDuplicates(response, new MyCallback() { @Override public void onResponse(Response response) { lastIsHasDuplicates = calcDuplicates(response); setHasDuplicates(lastIsHasDuplicates); checkEqualityToEnableButton(); } }); } }); } });
Явное состояние
43
private String lastPassText; private String lastConfirmText; private boolean lastIsHasDuplicates; private boolean lastIsNameEmpty;
44
lastPassText = editable.toString().trim();
45
lastPassText = editable.toString().trim();
46
lastPassText = editable.toString().trim();
47
private String lastPassText; private String lastConfirmText; private boolean lastIsHasDuplicates; private boolean lastIsNameEmpty; private String lastPassTrimmedString;
“Все может быть лучше”
“Пора стать реактивней”
50Потоки данных
t
Данные Ошибка Конец
51
t
52
M
Mt
53
Mo
M Mot
54
Mob
M Mo Mobt
55
Mobi
M Mo Mob Mobit
56
Mobiu
MobiuM Mo Mob Mobit
57
Mobius
MobiuM Mo Mob Mobi Mobiust
• изолируют состояние
58Потоки данных
• изолируют состояние
• прошлое
59Потоки данных
• изолируют состояние
• прошлое
• настоящее
60Потоки данных
• изолируют состояние
• прошлое
• настоящее
• будущее
61Потоки данных
• изолируют состояние
• прошлое
• настоящее
• будущее
• неизменяемые
62Потоки данных
• изолируют состояние
• прошлое
• настоящее
• будущее
• неизменяемые
• строго типизированные
63Потоки данных
• EditTextObservable
64Что может быть потоком?
• EditTextObservable
• ApiDataObservable
65Что может быть потоком?
• EditTextObservable
• ApiDataObservable
• TouchObservable
66Что может быть потоком?
• EditTextObservable
• ApiDataObservable
• TouchObservable
• Все, что захотите
67Что может быть потоком?
RxJava
• reactive streams для java
69RxJava
• reactive streams для java
• open source
• by Netflix
70RxJava
• reactive streams для java
• open source
• by Netflix
• стабильная
71RxJava
• reactive streams для java
• open source
• by Netflix
• стабильная
• почти
72RxJava
Observable
74Потоки данных
t
Данные Ошибка Конец
• создать
75Observable можно
• создать
• изменить
76Observable можно
• создать
• изменить
• получить значения
77Observable можно
78Создание
7
Observable<Integer> just = Observable.just(7);
t
79Создание
6541 2 3 7
Observable<Integer> many = Observable .from(Arrays.asList(1, 2, 3, 4, 5, 6, 7));
t
80СозданиеObservable.create(subscriber -> { final List<Data> list = requestNewData(); for (final Data data : list) { subscriber.onNext(data); } subscriber.onCompleted(); });
81СозданиеObservable.create(subscriber -> { final List<Data> list = requestNewData(); for (final Data data : list) { subscriber.onNext(data); } subscriber.onCompleted(); });
82СозданиеObservable.create(subscriber -> { final List<Data> list = requestNewData(); for (final Data data : list) { subscriber.onNext(data); } subscriber.onCompleted(); });
83СозданиеObservable.create(subscriber -> { final List<Data> list = requestNewData(); for (final Data data : list) { subscriber.onNext(data); } subscriber.onCompleted(); });
84Подписка
t
85Подписка
Кто-то №1
t
86Подписка
Кто-то №1
Mt
M
87Подписка
Кто-то №2
Mt
Кто-то №1
M
88Подписка
Кто-то №2
M Mot
Mo
Кто-то №1
89Подписка
Кто-то №2
M Mo Mobt
Mob
Кто-то №1
90
91
Observable<String> nameObs = EditTextObservable.from(nameEditText); Subscription nameSubs = nameObs .doOnNext(name -> { Log.i(TAG, "new user name : " + name); }) .subscribe(name -> { signUpTitle.setText("Sign up, " + name); });
92
Observable<String> nameObs = EditTextObservable.from(nameEditText); Subscription nameSubs = nameObs .doOnNext(name -> { Log.i(TAG, "new user name : " + name); }) .subscribe(name -> { signUpTitle.setText("Sign up, " + name); });
93
Observable<String> nameObs = EditTextObservable.from(nameEditText); Subscription nameSubs = nameObs .doOnNext(name -> { Log.i(TAG, "new user name : " + name); }) .subscribe(name -> { signUpTitle.setText("Sign up, " + name); });
94
// когда больше не надо слушать этот EditTextnameSubs.unsubscribe();
Observable<String> nameObs = EditTextObservable.from(nameEditText); Subscription nameSubs = nameObs .doOnNext(name -> { Log.i(TAG, "new user name : " + name); }) .subscribe(name -> { signUpTitle.setText("Sign up, " + name); });
95
Операторы
• возвращают новый Observable
97Операторы
• возвращают новый Observable
• применяются ко всему потоку
98Операторы
Основы : map и flatMap
100
102
Observable<String> nameObs = EditTextObservable .from(nameEditText);nameObs.subscribe(name -> requestDuplicates(name));Observable<String> rightTitleObservable = nameObs .map(name -> { if (TextUtils.isEmpty(name)) return name; else return ", " + name; });rightTitleObservable.subscribe(name -> signUpTitle.setText("Sign up" + name));
103
Observable<String> nameObs = EditTextObservable .from(nameEditText);nameObs.subscribe(name -> requestDuplicates(name));Observable<String> rightTitleObservable = nameObs .map(name -> { if (TextUtils.isEmpty(name)) return name; else return ", " + name; });rightTitleObservable.subscribe(name -> signUpTitle.setText("Sign up" + name));
104
Observable<String> nameObs = EditTextObservable .from(nameEditText);nameObs.subscribe(name -> requestDuplicates(name));Observable<String> rightTitleObservable = nameObs .map(name -> { if (TextUtils.isEmpty(name)) return name; else return ", " + name; });rightTitleObservable.subscribe(name -> signUpTitle.setText("Sign up" + name));
105
Observable<String> nameObs = EditTextObservable .from(nameEditText);nameObs.subscribe(name -> requestDuplicates(name));Observable<String> rightTitleObservable = nameObs .map(name -> { if (TextUtils.isEmpty(name)) return name; else return ", " + name; });rightTitleObservable.subscribe(name -> signUpTitle.setText("Sign up" + name));
106
Observable<String> nameObs = EditTextObservable .from(nameEditText);nameObs.subscribe(name -> requestDuplicates(name));Observable<String> rightTitleObservable = nameObs .map(name -> { if (TextUtils.isEmpty(name)) return name; else return ", " + name; });rightTitleObservable.subscribe(name -> signUpTitle.setText("Sign up" + name));
107
Observable<String> nameObs = EditTextObservable .from(nameEditText);nameObs.subscribe(name -> requestDuplicates(name));Observable<String> rightTitleObservable = nameObs .map(name -> { if (TextUtils.isEmpty(name)) return name; else return ", " + name; });rightTitleObservable.subscribe(name -> signUpTitle.setText("Sign up" + name));
108
109
110DebounceObservable<String> nameObs = EditTextObservable .from(nameEditText); Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .onErrorReturn(throwable -> false); duplicatesCheckObservable.subscribe(isUnique -> { /*process*/ });
111DebounceObservable<String> nameObs = EditTextObservable .from(nameEditText); Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .onErrorReturn(throwable -> false); duplicatesCheckObservable.subscribe(isUnique -> { /*process*/ });
112DebounceObservable<String> nameObs = EditTextObservable .from(nameEditText); Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .onErrorReturn(throwable -> false); duplicatesCheckObservable.subscribe(isUnique -> { /*process*/ });
113DebounceObservable<String> nameObs = EditTextObservable .from(nameEditText); Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .onErrorReturn(throwable -> false); duplicatesCheckObservable.subscribe(isUnique -> { /*process*/ });
• выполняется когда надо
114Наше решение
• выполняется когда надо
• экономит траффик
115Наше решение
• выполняется когда надо
• экономит траффик
• не нагружает сервер
116Наше решение
• выполняется когда надо
• экономит траффик
• не нагружает сервер
• без callback hell
117Наше решение
• выполняется когда надо
• экономит траффик
• не нагружает сервер
• без callback hell
• легко обрабатывает ошибки
118Наше решение
119
Прочие полезности
• debounce
121Полезности
• debounce
• cache
122Полезности
• debounce
• cache
• timeout
123Полезности
• debounce
• cache
• timeout
• first
124Полезности
• debounce
• cache
• timeout
• first
• distinct
125Полезности
• debounce
• cache
• timeout
• first
• distinct
• skipLast
126Полезности
• debounce
• cache
• timeout
• first
• distinct
• skipLast
• reduce
127Полезности
• debounce
• cache
• timeout
• first
• distinct
• skipLast
• reduce
• cast
128Полезности
• debounce
• cache
• timeout
• first
• distinct
• skipLast
• reduce
• cast
• averageDouble
129Полезности
• debounce
• cache
• timeout
• first
• distinct
• skipLast
• reduce
• cast
• averageDouble
• takeWhile
130Полезности
Комбинирование
132
133
134
Observable<Boolean> shouldEnableButtonObs = Observable .combineLatest( passObs.map(pass -> pass.trim()), confirmObs.map(confirm -> confirm.trim()), duplicatesCheckObservable, (pass, confirmPass, isNameUnique) -> TextUtils.equals(pass, confirmPass) && isNameUnique ); shouldEnableButtonObs .subscribe(enabled -> button.setEnabled(enabled));
135
Observable<Boolean> shouldEnableButtonObs = Observable .combineLatest( passObs.map(pass -> pass.trim()), confirmObs.map(confirm -> confirm.trim()), duplicatesCheckObservable, (pass, confirmPass, isNameUnique) -> TextUtils.equals(pass, confirmPass) && isNameUnique ); shouldEnableButtonObs .subscribe(enabled -> button.setEnabled(enabled));
136
Observable<Boolean> shouldEnableButtonObs = Observable .combineLatest( passObs.map(pass -> pass.trim()), confirmObs.map(confirm -> confirm.trim()), duplicatesCheckObservable, (pass, confirmPass, isNameUnique) -> TextUtils.equals(pass, confirmPass) && isNameUnique ); shouldEnableButtonObs .subscribe(enabled -> button.setEnabled(enabled));
137
Observable<Boolean> shouldEnableButtonObs = Observable .combineLatest( passObs.map(pass -> pass.trim()), confirmObs.map(confirm -> confirm.trim()), duplicatesCheckObservable, (pass, confirmPass, isNameUnique) -> TextUtils.equals(pass, confirmPass) && isNameUnique ); shouldEnableButtonObs .subscribe(enabled -> button.setEnabled(enabled));
138
Observable<Boolean> shouldEnableButtonObs = Observable .combineLatest( passObs.map(pass -> pass.trim()), confirmObs.map(confirm -> confirm.trim()), duplicatesCheckObservable, (pass, confirmPass, isNameUnique) -> TextUtils.equals(pass, confirmPass) && isNameUnique ); shouldEnableButtonObs .subscribe(enabled -> button.setEnabled(enabled));
139
А сколько у нас явного состояния?
141
// нисколько
• полезно знать все
142Операции
• полезно знать все
• применение есть всем. Ищите!
143Операции
• полезно знать все
• применение есть всем. Ищите!
• смотрите исходники
144Операции
• полезно знать все
• применение есть всем. Ищите!
• смотрите исходники
• думайте!
145Операции
Когда тредов > 1
Scheduler
• имеет пул потоков
148Scheduler
• имеет пул потоков
• создает Scheduler.Worker
149Scheduler
• имеет пул потоков
• создает Scheduler.Worker
• Worker –– цепочка последовательных вычислений
150Scheduler
subscribeOn observeOn
1. observeOn для того, что ниже по коду
2. subscribeOn для того, что выше по коду
3. Каждый следующий observeOn заменяет предыдущий
4. subscribeOn актуален только до первого observeOn
Оба оператора все еще возвращают новый поток
157
Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .onErrorReturn(throwable -> false);
158
Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .onErrorReturn(throwable -> false);
159
Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .observeOn(Schedulers.newThread()) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .subscribeOn(mainThread) .observeOn(mainThread) .onErrorReturn(throwable -> false);
160
Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .observeOn(Schedulers.newThread()) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .subscribeOn(mainThread) .observeOn(mainThread) .onErrorReturn(throwable -> false);
161
Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .observeOn(Schedulers.newThread()) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .subscribeOn(mainThread) .observeOn(mainThread) .onErrorReturn(throwable -> false);
162
Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .observeOn(Schedulers.newThread()) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .subscribeOn(mainThread) .observeOn(mainThread) .onErrorReturn(throwable -> false);
163
Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .observeOn(Schedulers.newThread()) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .subscribeOn(mainThread) .observeOn(mainThread) .onErrorReturn(throwable -> false);
164
Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .observeOn(Schedulers.newThread()) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .subscribeOn(mainThread) .observeOn(mainThread) .onErrorReturn(throwable -> false);
165
Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .observeOn(Schedulers.newThread()) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .subscribeOn(mainThread) .observeOn(mainThread) .onErrorReturn(throwable -> false);
166
Observable<Boolean> duplicatesCheckObservable = nameObs .debounce(500, TimeUnit.MILLISECONDS) .observeOn(Schedulers.newThread()) .map(name -> requestDuplicates(name)) .map(response -> checkResponseForDuplicates(response)) .map(checkedResp -> calcDuplicates(checkedResp)) .subscribeOn(mainThread) .observeOn(mainThread) .onErrorReturn(throwable -> false); duplicatesCheckObservable.subscribe(isUnique -> { /*process*/ });
Ложечка дегтя
• объектов аллоцируется больше
168Ложечка дегтя
• объектов аллоцируется больше
• сложно дебажить
169Ложечка дегтя
• объектов аллоцируется больше
• сложно дебажить
• логируйте много
170Ложечка дегтя
• объектов аллоцируется больше
• сложно дебажить
• логируйте много
• пишите правильно сразу
171Ложечка дегтя
• объектов аллоцируется больше
• сложно дебажить
• логируйте много
• пишите правильно сразу
• сложно объяснить
172Ложечка дегтя
• объектов аллоцируется больше
• сложно дебажить
• логируйте много
• пишите правильно сразу
• сложно объяснить
• сложно найти крутую команду
173Ложечка дегтя
Итоги
• изолированное состояние
175Итоги
• изолированное состояние
• легкая многопоточность
176Итоги
• изолированное состояние
• легкая многопоточность
• легкое комбинирование
177Итоги
• изолированное состояние
• легкая многопоточность
• легкое комбинирование
• понятность кода
178Итоги
• изолированное состояние
• легкая многопоточность
• легкое комбинирование
• понятность кода
• стойкость к ошибкам
179Итоги
Reactive Extension
Reactive Extension
Reactive Extension
Это круто!
Меняйтесь
За этим будущее
Правильная реализация подсветки кнопки
Observable<String> nameObs = EditTextObservable .from(nameEditText);Observable<Boolean> duplicatesCheckObservable2 = nameObs .flatMap(name -> DebouncedDuplicatesObservable .from(name) .startWith(false)) .onErrorReturn(thr -> false); duplicatesCheckObservable2. subscribe(isDubl -> { /*process*/ });
Observable<String> nameObs = EditTextObservable .from(nameEditText);Observable<Boolean> duplicatesCheckObservable2 = nameObs .flatMap(name -> DebouncedDuplicatesObservable .from(name) .startWith(false)) .onErrorReturn(thr -> false); duplicatesCheckObservable2. subscribe(isDubl -> { /*process*/ });
Observable<String> nameObs = EditTextObservable .from(nameEditText);Observable<Boolean> duplicatesCheckObservable2 = nameObs .flatMap(name -> DebouncedDuplicatesObservable .from(name) .startWith(false)) .onErrorReturn(thr -> false); duplicatesCheckObservable2. subscribe(isDubl -> { /*process*/ });
Observable<String> nameObs = EditTextObservable .from(nameEditText);Observable<Boolean> duplicatesCheckObservable2 = nameObs .flatMap(name -> DebouncedDuplicatesObservable .from(name) .startWith(false)) .onErrorReturn(thr -> false); duplicatesCheckObservable2. subscribe(isDubl -> { /*process*/ });
• Reactive Streams
• Akka Stream
• Reactor
• Ratpack
• RxJava
• Reactive Manifesto
192Ссылки
• Reactive Extensions
• Reactive Extensions for JavaScript
• Reactive Cocoa
• Rx.py
• Rx.php
193Ссылки