The Art of RxМатвей Мальков
2Обо мне
3Обо мне
4Обо мне
5Обо мне
Èñêóññòâî ðåàêòèâíîãî ïðîãðàììèðîâàíèÿ
Èñêóññòâî – âûñîêàÿ ñòåïåíü ìàñòåðñòâà â êàêîé-ëèáî
ñôåðå äåÿòåëüíîñòè.
Íàø ãëàâíûé íàâûê – óìåíèå äóìàòü
9Умение думать. Практика
• î ÷åì äóìàòü?
9Умение думать. Практика
• î ÷åì äóìàòü?
• êàê äóìàòü?
9Умение думать. Практика
10О чем думать мобильному разработчику
• ýíåðãîïîòðåáëåíèå
10О чем думать мобильному разработчику
• ýíåðãîïîòðåáëåíèå
• ïëàâíîñòü èíòåðôåéñà (60 fps)
10О чем думать мобильному разработчику
• ýíåðãîïîòðåáëåíèå
• ïëàâíîñòü èíòåðôåéñà (60 fps)
• ôðàãìåíòàöèÿ óñòðîéñòâ
10О чем думать мобильному разработчику
• ýíåðãîïîòðåáëåíèå
• ïëàâíîñòü èíòåðôåéñà (60 fps)
• ôðàãìåíòàöèÿ óñòðîéñòâ
• ïîòðåáëåíèå ïàìÿòè
10О чем думать мобильному разработчику
11Как думать мобильному разработчику?
• êàê ïîëüçîâàòåëü
11Как думать мобильному разработчику?
• êàê ïîëüçîâàòåëü
• êàê äèçàéíåð
11Как думать мобильному разработчику?
• êàê ïîëüçîâàòåëü
• êàê äèçàéíåð
• êàê ïðåäëàãàåò ôóíäàìåíòàëüíûé ôðåéìâîðê
11Как думать мобильному разработчику?
12Фундаментальный фреймворк
• äåëàåò ðàçðàáîòêó ïðîùå/ëó÷øå/áûñòðåå
12Фундаментальный фреймворк
• äåëàåò ðàçðàáîòêó ïðîùå/ëó÷øå/áûñòðåå
• ïðèâíîñèò ÷òî-òî êîíöåïòóàëüíî íîâîå
12Фундаментальный фреймворк
• äåëàåò ðàçðàáîòêó ïðîùå/ëó÷øå/áûñòðåå
• ïðèâíîñèò ÷òî-òî êîíöåïòóàëüíî íîâîå
• ïðîðàñòàåò âî âñå ïîäñèñòåìû
12Фундаментальный фреймворк
• äåëàåò ðàçðàáîòêó ïðîùå/ëó÷øå/áûñòðåå
• ïðèâíîñèò ÷òî-òî êîíöåïòóàëüíî íîâîå
• ïðîðàñòàåò âî âñå ïîäñèñòåìû
• ñëîæíî óáðàòü
12Фундаментальный фреймворк
• äåëàåò ðàçðàáîòêó ïðîùå/ëó÷øå/áûñòðåå
• ïðèâíîñèò ÷òî-òî êîíöåïòóàëüíî íîâîå
• ïðîðàñòàåò âî âñå ïîäñèñòåìû
• ñëîæíî óáðàòü
• òðåáóåò íàâûêîâ îáðàùåíèÿ
12Фундаментальный фреймворк
Íàø ëþáèìûé ôóíäàìåíòàëüíûé ôðåéìâîðê – RxJava
Î ÷eì äóìàòü, èñïîëüçóÿ RxJava?
Î êîíòðàêòàõ
16О контрактах
• ïðîáëåìû:
16О контрактах
• ïðîáëåìû:
• ïîíèìàíèå
16О контрактах
• ïðîáëåìû:
• ïîíèìàíèå
• ñîáëþäåíèå
16О контрактах
• ïðîáëåìû:
• ïîíèìàíèå
• ñîáëþäåíèå
• âàæíî:
16О контрактах
• ïðîáëåìû:
• ïîíèìàíèå
• ñîáëþäåíèå
• âàæíî:
• îñíîâû
16О контрактах
17Нотификации
• onNext – 0, n èëè inf
17Нотификации
• onNext – 0, n èëè inf
• onCompleted – 0 èëè 1
17Нотификации
• onNext – 0, n èëè inf
• onCompleted – 0 èëè 1
• onError – 0 èëè 1
17Нотификации
• onNext – 0, n èëè inf
• onCompleted – 0 èëè 1
• onError – 0 èëè 1
• onCompleted + onError <= 1
17Нотификации
18Подписка и отписка
• íå íóæíî îòïèñûâàòüñÿ ïîñëå çàâåðøåíèÿ
18Подписка и отписка
• íå íóæíî îòïèñûâàòüñÿ ïîñëå çàâåðøåíèÿ
• ïðè îòïèñêå çàâåðøåíèå íå ãàðàíòèðîâàíî
18Подписка и отписка
19Observable<Response> obs = Observable .interval(1, TimeUnit.SECONDS) .map(this!::sendPing) .cache();
20Observable<Response> obs = Observable .interval(1, TimeUnit.SECONDS) .map(this!::sendPing) .cache();
21Observable<Response> obs = Observable .interval(1, TimeUnit.SECONDS) .map(this!::sendPing) .cache();
22Observable<Response> obs = Observable .interval(1, TimeUnit.SECONDS) .map(this!::sendPing) .cache();
23Observable<Response> obs = Observable .interval(1, TimeUnit.SECONDS) .map(this!::sendPing) .cache();Subscription sub = obs.subscribe();
24Observable<Response> obs = Observable .interval(1, TimeUnit.SECONDS) .map(this!::sendPing) .cache();Subscription sub = obs.subscribe();!//позжеsub.unsubscribe();
25Observable<Response> obs = Observable .interval(1, TimeUnit.SECONDS) .map(this!::sendPing) .cache();Subscription sub = obs.subscribe();!//позжеsub.unsubscribe();
Note: You sacrifice the ability to unsubscribe from the origin
26Observable<Response> obs = Observable .interval(1, TimeUnit.SECONDS) .map(this!::sendPing) .cache();Subscription sub = obs.subscribe();!//позжеsub.unsubscribe();
Note: You sacrifice the ability to unsubscribe from the origin
27О контрактах
• îòïðàâêà íîòèôèêàöèé
• çàâåðøåíèå ñ îøèáêîé
27О контрактах
• îòïðàâêà íîòèôèêàöèé
• çàâåðøåíèå ñ îøèáêîé
• ïîëíûé ñïèñîê : http://reactivex.io/documentation/contract.html
27О контрактах
28Состояние системы
• èçîëèðîâàííî
28Состояние системы
• èçîëèðîâàííî
• îäèí èñòî÷íèê
28Состояние системы
• èçîëèðîâàííî
• îäèí èñòî÷íèê
• íåèçìåíÿåìî
28Состояние системы
• èçîëèðîâàííî
• îäèí èñòî÷íèê
• íåèçìåíÿåìî
• ïîä÷èíÿåòñÿ êîíòðàêòó Observable
28Состояние системы
29
nameEditTextChanges.subscribe(this!::updateName);
30
nameEditTextChanges.subscribe(this!::updateName);
PublishSubject<Integer> nameEditTextChanges = PublishSubject.create();
31
nameEditTextChanges.subscribe(this!::updateName);
PublishSubject<Integer> nameEditTextChanges = PublishSubject.create();
nameEditTextChanges.onNext("хаха, обманул!");
Subject – âîçìîæíîå íàðóøåíèå êîíòðàêòà
Subject – âîçìîæíîå íàðóøåíèå êîíòðàêòà
• èçîëèðîâàííî
• îäèí èñòî÷íèê
• íåèçìåíÿåìî
• ïîä÷èíÿåòñÿ êîíòðàêòó Observable
34Состояние системы
35Subjects
• ñîåäèíåíèå èìïåðàòèâíîãî è ðåàêòèâíîãî ìèðà
35Subjects
• ñîåäèíåíèå èìïåðàòèâíîãî è ðåàêòèâíîãî ìèðà
• êîãäà íåâîçìîæíî ñäåëàòü ïî äðóãîìó
35Subjects
• ñîåäèíåíèå èìïåðàòèâíîãî è ðåàêòèâíîãî ìèðà
• êîãäà íåâîçìîæíî ñäåëàòü ïî äðóãîìó
• ýòî âàø ñëó÷àé? ÍÅÒ!
35Subjects
• ñîåäèíåíèå èìïåðàòèâíîãî è ðåàêòèâíîãî ìèðà
• êîãäà íåâîçìîæíî ñäåëàòü ïî äðóãîìó
• ýòî âàø ñëó÷àé? ÍÅÒ!
• âñå òàêè âàø? Íåò, ýòî íå òàê!
35Subjects
• ñîåäèíåíèå èìïåðàòèâíîãî è ðåàêòèâíîãî ìèðà
• êîãäà íåâîçìîæíî ñäåëàòü ïî äðóãîìó
• ýòî âàø ñëó÷àé? ÍÅÒ!
• âñå òàêè âàø? Íåò, ýòî íå òàê!
• ìåíüøå – ëó÷øå
35Subjects
О тредах
37О тредах
• ïðîáëåìû
37О тредах
• ïðîáëåìû
• íåò ïîíèìàÿ ðàáîòû
37О тредах
• ïðîáëåìû
• íåò ïîíèìàÿ ðàáîòû
• ñóþò subscribeOn è observeOn âåçäå ïîäðÿä
37О тредах
• ïðîáëåìû
• íåò ïîíèìàÿ ðàáîòû
• ñóþò subscribeOn è observeOn âåçäå ïîäðÿä
• âàæíî :
37О тредах
• ïðîáëåìû
• íåò ïîíèìàÿ ðàáîòû
• ñóþò subscribeOn è observeOn âåçäå ïîäðÿä
• âàæíî :
• ðàçáèðàòüñÿ
37О тредах
38Scheduler
• ïëàíèðîâêà çàäà÷
38Scheduler
• ïëàíèðîâêà çàäà÷
• ðàáîòà ñ òðåäàìè
38Scheduler
• ïëàíèðîâêà çàäà÷
• ðàáîòà ñ òðåäàìè
• subscribeOn
38Scheduler
• ïëàíèðîâêà çàäà÷
• ðàáîòà ñ òðåäàìè
• subscribeOn
• observeOn
38Scheduler
Äîãìû observeOn
subscribeOn
1. observeOn ìåíÿåò Scheduler äëÿ âñåãî, ÷òî íèæå ïî êîäó
2. subscribeOn ìåíÿåò Scheduler ñ ñàìîãî âåðõà öåïî÷êè
3. Êàæäûé ñëåäóþùèé observeOn çàìåíÿåò ïðåäûäóùèé
4. subscribeOn àêòóàëåí äî ïåðâîãî observeOn
Âñåãäà äóìàéòå î òîì, â êàêîì òðåäå âûïîëíÿåòñÿ ôóíêöèÿ
45
Observable<Response> requestObs = userDbChanges .filter(u 0-> u.age < 18) .map(this!::toRequestModel) .map(this!::doRequest) .onErrorReturn(this!::doRecoverReq) .map(this!::cacheAndModify);!//ui threadrequestObs.subscribe(this!::showOnUi);
46
Observable<Response> requestObs = userDbChanges .filter(u 0-> u.age < 18) .map(this!::toRequestModel) .map(this!::doRequest) .onErrorReturn(this!::doRecoverReq) .map(this!::cacheAndModify);!//ui threadrequestObs.subscribe(this!::showOnUi);
47
Observable<Response> requestObs = userDbChanges .filter(u 0-> u.age < 18) .map(this!::toRequestModel) .observeOn(networkScheduler) .map(this!::doRequest) .onErrorReturn(this!::doRecoverReq) .subscribeOn(Schedulers.computation()) .observeOn(mainThread) .map(this!::toUiModel);!//ui threadrequestObs.subscribe(this!::showOnUi);
48
Observable<Response> requestObs = userDbChanges .filter(u 0-> u.age < 18) .map(this!::toRequestModel) .observeOn(networkScheduler) .map(this!::doRequest) .onErrorReturn(this!::doRecoverReq) .subscribeOn(Schedulers.computation()) .observeOn(mainThread) .map(this!::toUiModel);!//ui threadrequestObs.subscribe(this!::showOnUi);
49
Observable<Response> requestObs = userDbChanges .filter(u 0-> u.age < 18) .map(this!::toRequestModel) .observeOn(networkScheduler) .map(this!::doRequest) .onErrorReturn(this!::doRecoverReq) .subscribeOn(Schedulers.computation()) .observeOn(mainThread) .map(this!::toUiModel);!//ui threadrequestObs.subscribe(this!::showOnUi);
50
Observable<Response> requestObs = userDbChanges .filter(u 0-> u.age < 18) .map(this!::toRequestModel) .observeOn(networkScheduler) .map(this!::doRequest) .onErrorReturn(this!::doRecoverReq) .subscribeOn(Schedulers.computation()) .observeOn(mainThread) .map(this!::toUiModel);!//ui threadrequestObs.subscribe(this!::showOnUi);
51
Observable<Response> requestObs = userDbChanges .filter(u 0-> u.age < 18) .map(this!::toRequestModel) .observeOn(networkScheduler) .map(this!::doRequest) .onErrorReturn(this!::doRecoverReq) .subscribeOn(Schedulers.computation()) .observeOn(mainThread) .map(this!::toUiModel);!//ui threadrequestObs.subscribe(this!::showOnUi);
52
Observable<Response> requestObs = userDbChanges .filter(u 0-> u.age < 18) .map(this!::toRequestModel) .observeOn(networkScheduler) .map(this!::doRequest) .onErrorReturn(this!::doRecoverReq) .subscribeOn(Schedulers.computation()) .observeOn(mainThread) .map(this!::toUiModel);!//ui threadrequestObs.subscribe(this!::showOnUi);
53
Observable<Response> requestObs = userDbChanges .filter(u 0-> u.age < 18) .map(this!::toRequestModel) .observeOn(networkScheduler) .map(this!::doRequest) .onErrorReturn(this!::doRecoverReq) .subscribeOn(Schedulers.computation()) .observeOn(mainThread) .map(this!::toUiModel);!//ui threadrequestObs.subscribe(this!::showOnUi);
Âåëèêîëåïíûé subscribeOn
55subscribeOn
public final Observable<T> subscribeOn(Scheduler scheduler) { !//какой-то неважный код return nest().lift(new OperatorSubscribeOn<T>(scheduler));}
56subscribeOn
public final Observable<T> subscribeOn(Scheduler scheduler) { !//какой-то неважный код return nest().lift(new OperatorSubscribeOn<T>(scheduler));}
57
public final Observable<Observable<T9>> nest() { return just(this);}
58
59
nest
60
nest
61subscribeOn
public final Observable<T> subscribeOn(Scheduler scheduler) { !//какой-то неважный код return nest().lift(new OperatorSubscribeOn<T>(scheduler));}
62subscribeOn
public final Observable<T> subscribeOn(Scheduler scheduler) { !//какой-то неважный код return nest().lift(new OperatorSubscribeOn<T>(scheduler));}
63subscribeOn
public final Observable<T> subscribeOn(Scheduler scheduler) { !//какой-то неважный код return nest().lift(new OperatorSubscribeOn<T>(scheduler));}
64
public class OperatorSubscribeOn<T> implements Operator<T, Observable<T9>> { private final Scheduler scheduler; public OperatorSubscribeOn(Scheduler scheduler) { this.scheduler = scheduler; } @Override public Subscriber<? super Observable<T9>> call(final Subscriber<? super T> subscriber) { … }
65
public class OperatorSubscribeOn<T> implements Operator<T, Observable<T9>> { private final Scheduler scheduler; public OperatorSubscribeOn(Scheduler scheduler) { this.scheduler = scheduler; } @Override public Subscriber<? super Observable<T9>> call(final Subscriber<? super T> subscriber) { … }
принимаетвозвращает
66
public class OperatorSubscribeOn<T> implements Operator<T, Observable<T9>> { private final Scheduler scheduler; public OperatorSubscribeOn(Scheduler scheduler) { this.scheduler = scheduler; } @Override public Subscriber<? super Observable<T9>> call(final Subscriber<? super T> subscriber) { … }
67@Overridepublic Subscriber<? super Observable<T9>> call(final Subscriber<? super T> subscriber) { final Worker inner = scheduler.createWorker(); subscriber.add(inner); return new Subscriber<Observable<T9>>(subscriber) { @Override public void onNext(final Observable<T> o) { inner.schedule(new Action0() { @Override public void call() { final Thread t = Thread.currentThread(); o.unsafeSubscribe(new Subscriber<T>(subscriber) { !//обычный inner subscribe } !//тут еще оооочень много закрывающихся скобочек}
68@Overridepublic Subscriber<? super Observable<T9>> call(final Subscriber<? super T> subscriber) { final Worker inner = scheduler.createWorker(); subscriber.add(inner); return new Subscriber<Observable<T9>>(subscriber) { @Override public void onNext(final Observable<T> o) { inner.schedule(new Action0() { @Override public void call() { final Thread t = Thread.currentThread(); o.unsafeSubscribe(new Subscriber<T>(subscriber) { !//обычный inner subscribe } !//тут еще оооочень много закрывающихся скобочек}
69@Overridepublic Subscriber<? super Observable<T9>> call(final Subscriber<? super T> subscriber) { final Worker inner = scheduler.createWorker(); subscriber.add(inner); return new Subscriber<Observable<T9>>(subscriber) { @Override public void onNext(final Observable<T> o) { inner.schedule(new Action0() { @Override public void call() { final Thread t = Thread.currentThread(); o.unsafeSubscribe(new Subscriber<T>(subscriber) { !//обычный inner subscribe } !//тут еще оооочень много закрывающихся скобочек}
70@Overridepublic Subscriber<? super Observable<T9>> call(final Subscriber<? super T> subscriber) { final Worker inner = scheduler.createWorker(); subscriber.add(inner); return new Subscriber<Observable<T9>>(subscriber) { @Override public void onNext(final Observable<T> o) { inner.schedule(new Action0() { @Override public void call() { final Thread t = Thread.currentThread(); o.unsafeSubscribe(new Subscriber<T>(subscriber) { !//обычный inner subscribe } !//тут еще оооочень много закрывающихся скобочек}
71@Overridepublic Subscriber<? super Observable<T9>> call(final Subscriber<? super T> subscriber) { final Worker inner = scheduler.createWorker(); subscriber.add(inner); return new Subscriber<Observable<T9>>(subscriber) { @Override public void onNext(final Observable<T> o) { inner.schedule(new Action0() { @Override public void call() { final Thread t = Thread.currentThread(); o.unsafeSubscribe(new Subscriber<T>(subscriber) { !//обычный inner subscribe } !//тут еще оооочень много закрывающихся скобочек}
Наш Observable
72@Overridepublic Subscriber<? super Observable<T9>> call(final Subscriber<? super T> subscriber) { final Worker inner = scheduler.createWorker(); subscriber.add(inner); return new Subscriber<Observable<T9>>(subscriber) { @Override public void onNext(final Observable<T> o) { inner.schedule(new Action0() { @Override public void call() { final Thread t = Thread.currentThread(); o.unsafeSubscribe(new Subscriber<T>(subscriber) { !//обычный inner subscribe } !//тут еще оооочень много закрывающихся скобочек}
73@Overridepublic Subscriber<? super Observable<T9>> call(final Subscriber<? super T> subscriber) { final Worker inner = scheduler.createWorker(); subscriber.add(inner); return new Subscriber<Observable<T9>>(subscriber) { @Override public void onNext(final Observable<T> o) { inner.schedule(new Action0() { @Override public void call() { final Thread t = Thread.currentThread(); o.unsafeSubscribe(new Subscriber<T>(subscriber) { !//обычный inner subscribe } !//тут еще оооочень много закрывающихся скобочек}
74
75
OperatorSubscribeOn(Scheduler)
76
OperatorSubscribeOn(Scheduler)
Ãåíèàëüíî!
78
Observable.just(1) .subscribeOn(scheduler1) .subscribeOn(scheduler2) .subscribe();
79
Observable.just(1) .subscribeOn(scheduler1) .subscribeOn(scheduler2) .subscribe(); !//scheduler1
2. subscribeOn ìåíÿåò Scheduler ñ ñàìîãî âåðõà öåïî÷êè
2. Ïåðâûé subscribeOn ìåíÿåò Scheduler ñ ñàìîãî âåðõà öåïî÷êè
82
!//computationObservable<Long> o1 = Observable.interval(1, TimeUnit.SECONDS); !//networkObservable<Integer> o2 = Observable.just(1) .observeOn(networkScheduler);Observable.zip(o1, o2, o3, (r1, r2) 0-> r1 * r2);
83
!//computationObservable<Long> o1 = Observable.interval(1, TimeUnit.SECONDS); !//networkObservable<Integer> o2 = Observable.just(1) .observeOn(networkScheduler);Observable.zip(o1, o2, o3, (r1, r2) 0-> r1 * r2);
84
!//computationObservable<Long> o1 = Observable.interval(1, TimeUnit.SECONDS); !//networkObservable<Integer> o2 = Observable.just(1) .observeOn(networkScheduler);Observable.zip(o1, o2, o3, (r1, r2) 0-> r1 * r2); !//computation or network
85final class InnerSubscriber extends Subscriber { @Override public void onNext(Object t) { try { items.onNext(t); } catch (MissingBackpressureException e) { onError(e); } tick(); }};
86final class InnerSubscriber extends Subscriber { @Override public void onNext(Object t) { try { items.onNext(t); } catch (MissingBackpressureException e) { onError(e); } tick(); }};
87
void tick() { !//тут много кода, не имеющего сейчас значения if (requested.get() > 0 !&& allHaveValues) { try { !// всем потокам есть что zip’ать child.onNext(zipFunction.call(vs));
} !// еще много кода и скобочки
88
void tick() { !//тут много кода, не имеющего сейчас значения if (requested.get() > 0 !&& allHaveValues) { try { !// всем потокам есть что zip’ать child.onNext(zipFunction.call(vs));
} !// еще много кода и скобочки
89
void tick() { !//тут много кода, не имеющего сейчас значения if (requested.get() > 0 !&& allHaveValues) { try { !// всем потокам есть что zip’ать child.onNext(zipFunction.call(vs));
} !// еще много кода и скобочки
Âñåãäà äóìàéòå î òîì, â êàêîì òðåäå âûïîëíÿåòñÿ ôóíêöèÿ
О количестве элементов
92Количество элементов
• ïðîáëåìû:
92Количество элементов
• ïðîáëåìû:
• áîëüøå ýëåìåíòîâ – áîëüøå ìóñîðà
92Количество элементов
• ïðîáëåìû:
• áîëüøå ýëåìåíòîâ – áîëüøå ìóñîðà
• RxJava ïîñòîÿííî âñå êîïèðóåò
92Количество элементов
• ïðîáëåìû:
• áîëüøå ýëåìåíòîâ – áîëüøå ìóñîðà
• RxJava ïîñòîÿííî âñå êîïèðóåò
• âàæíî:
92Количество элементов
• ïðîáëåìû:
• áîëüøå ýëåìåíòîâ – áîëüøå ìóñîðà
• RxJava ïîñòîÿííî âñå êîïèðóåò
• âàæíî:
• íå ïàíèêîâàòü
92Количество элементов
• ïðîáëåìû:
• áîëüøå ýëåìåíòîâ – áîëüøå ìóñîðà
• RxJava ïîñòîÿííî âñå êîïèðóåò
• âàæíî:
• íå ïàíèêîâàòü
• çàáîòèòüñÿ î GC
92Количество элементов
93Не паниковать
• «×òî ñ GC?» : ñàìûé æàðêèé âîïðîñ
93Не паниковать
• «×òî ñ GC?» : ñàìûé æàðêèé âîïðîñ
• êîïèðîâàíèå òîëüêî ïðè íîòèôèêàöèÿõ
93Не паниковать
• «×òî ñ GC?» : ñàìûé æàðêèé âîïðîñ
• êîïèðîâàíèå òîëüêî ïðè íîòèôèêàöèÿõ
• âñå îïòèìèçèðîâàííî
93Не паниковать
94
locationUpdates() .distinctUntilChanged() .subscribe();
95
locationUpdates() .distinctUntilChanged() .subscribe();
96
locationUpdates() .distinctUntilChanged() .subscribe();
touchEvents(view) .filter(e 0-> e.x > minWidth !&& e.y > minHeight) .doOnNext(this!::processEvents) .subscribe();
97
locationUpdates() .distinctUntilChanged() .subscribe();
touchEvents(view) .filter(e 0-> e.x > minWidth !&& e.y > minHeight) .doOnNext(this!::processEvents) .subscribe();
98
nameEditTextChanges .map(this!::prepareForSending) .map(this!::sendName) .map(this!::cacheAndModify);
99
nameEditTextChanges .map(this!::prepareForSending) !//раз .map(this!::sendName) .map(this!::cacheAndModify);
100
nameEditTextChanges .map(this!::prepareForSending) !//раз .map(this!::sendName) !// два .map(this!::cacheAndModify);
101
nameEditTextChanges .map(this!::prepareForSending) !//раз .map(this!::sendName) !// два .map(this!::cacheAndModify); !// три
102
nameEditTextChanges .debounce(500, TimeUnit.MILLISECONDS) .map(this!::prepareForSending) .map(this!::sendName) .map(this!::cacheAndModify);
103debounce
• áåðåæåò GC
103debounce
• áåðåæåò GC
• áåðåæåò òðàôèê ïîëüçîâàòåëÿ
103debounce
• áåðåæåò GC
• áåðåæåò òðàôèê ïîëüçîâàòåëÿ
• áåðåæåò íàñ îò BackPressure
103debounce
104Backpressure
• îòäàþùèé òðåä äàåò ìíîãî
104Backpressure
• îòäàþùèé òðåä äàåò ìíîãî
• ïðèíèìàþùèé òðåä íå óñïåâàåò
104Backpressure
• îòäàþùèé òðåä äàåò ìíîãî
• ïðèíèìàþùèé òðåä íå óñïåâàåò
• â ìíîãîïîòî÷íîé ñðåäå ìíîãîâåðîÿòíî
104Backpressure
• îòäàþùèé òðåä äàåò ìíîãî
• ïðèíèìàþùèé òðåä íå óñïåâàåò
• â ìíîãîïîòî÷íîé ñðåäå ìíîãîâåðîÿòíî
• ïåðâûé ïðèçíàê – observeOn
104Backpressure
105
events
touchEvents(view)
106
events filter
touchEvents(view) .filter(e 0-> e.x > minWidth)
107
events filter map
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize)
108
events filter map observeOn
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler)
109
events filter map observeOn
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending)
map
110
events filter map observeOn
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest)
map map
111
events filter map observeOn
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
map map subscriber
112
events filter map observeOn
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
map map subscriber
113
events filter map observeOn
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
map map subscriber
114
events filter map observeOn
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
map map subscriber
115
events filter map observeOn
map map subscribertouchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
116
events filter map observeOn
map map subscribertouchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
117
events filter map observeOn
map map subscribertouchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
118
events filter map observeOn
map map subscribertouchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
119
events filter map observeOn
map map subscribertouchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
120
events filter map observeOn
map map subscribertouchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
121
events filter map observeOn
map map subscribertouchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
122
events filter map observeOn
map map subscribertouchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
123
events filter map observeOn
map map subscribertouchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
124
events filter map observeOn
map map subscribertouchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
125
events filter map observeOn
map map subscriber
2
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
126
events filter map observeOn
map map subscriber
2
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
127
events filter map observeOn
map map subscriber
2
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
128
events filter map observeOn
map map subscriber
2
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
129
events filter map observeOn
map map subscriber
3
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
130
events filter map observeOn
map map subscriber
3
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
131
events filter map observeOn
map map subscriber
3
Устал
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
132
events filter map observeOn
map map subscriber
3
Устал
touchEvents(view) .filter(e 0-> e.x > minWidth) .map(this!::normalize) .observeOn(networkScheduler) .map(this!::prepareForSending) .map(this!::doMotionRequest) .subscribe()
133
events filter map observeOn
map map subscriber
4
rx.exceptions.MissingBackpressureException
134
Observable.zip( Observable.interval(10, TimeUnit.MILLISECONDS), Observable.interval(20, TimeUnit.MILLISECONDS), (r1, r2) 0-> r1 * r2).subscribe();
135
Observable.zip( Observable.interval(10, TimeUnit.MILLISECONDS), Observable.interval(20, TimeUnit.MILLISECONDS), (r1, r2) 0-> r1 * r2).subscribe();
136
Observable.zip( Observable.interval(10, TimeUnit.MILLISECONDS), Observable.interval(20, TimeUnit.MILLISECONDS), (r1, r2) 0-> r1 * r2).subscribe();
137
Observable.zip( Observable.interval(10, TimeUnit.MILLISECONDS), Observable.interval(20, TimeUnit.MILLISECONDS), (r1, r2) 0-> r1 * r2).subscribe();
138
Observable.zip( Observable.interval(10, TimeUnit.MILLISECONDS), Observable.interval(20, TimeUnit.MILLISECONDS), (r1, r2) 0-> r1 * r2).subscribe();
rx.exceptions.MissingBackpressureException
139Борьба с Backpressure
• debounce, throttle
139Борьба с Backpressure
• debounce, throttle
• window, buffer
139Борьба с Backpressure
• debounce, throttle
• window, buffer
• onBackpressureBuffer
139Борьба с Backpressure
• debounce, throttle
• window, buffer
• onBackpressureBuffer
• onBackpressureDrop
139Борьба с Backpressure
140
141
Âñåãäà äóìàéòå î òîì, ñêîëüêî ýëåìåíòîâ âàì íóæíî
îò Observable
Êàê äóìàòü, èñïîëüçóÿ RxJava?
Как инженер
145Как инженер
• íå áîéñÿ ðàñøèðÿòü ôðåéìâîðê
145Как инженер
• íå áîéñÿ ðàñøèðÿòü ôðåéìâîðê
• ïîíèìàé, êàê îí ðàáîòàåò âíóòðè
145Как инженер
146DoOnNextAsync
• doOnNext == ñàéä ýôôåêò
146DoOnNextAsync
• doOnNext == ñàéä ýôôåêò
• íå áëîêèðîâàòü âûïîëíåíèå ðîäèòåëÿ
146DoOnNextAsync
• doOnNext == ñàéä ýôôåêò
• íå áëîêèðîâàòü âûïîëíåíèå ðîäèòåëÿ
• áûë ïðèâÿçàí ê æèçíåííîìó öèêëó ðîäèòåëÿ
146DoOnNextAsync
• doOnNext == ñàéä ýôôåêò
• íå áëîêèðîâàòü âûïîëíåíèå ðîäèòåëÿ
• áûë ïðèâÿçàí ê æèçíåííîìó öèêëó ðîäèòåëÿ
• íå ÷åðåç Subjects
146DoOnNextAsync
147
userDbChanges .map(this!::doRequest) .doOnNext(resp 0-> { globalSubscription = createCacheObs(resp) .subscribe(); }) .map(this!::doAnotherRequest);
148
userDbChanges .map(this!::doRequest) .doOnNext(resp 0-> { globalSubscription = createCacheObs(resp) .subscribe(); }) .map(this!::doAnotherRequest);
149
userDbChanges .map(this!::doRequest) .lift(new DoOnNextAsyncG<>(this!::cacheObs)) .map(this!::doAnotherRequest);
150
userDbChanges .map(this!::doRequest) .lift(new DoOnNextAsyncG<>(this!::cacheObs)) .map(this!::doAnotherRequest);
151
class DoOnNextAsync<F, T> implements Observable.Operator<F, F> { private final Func1<F, Observable<T9>> nested; public DoOnNextAsync(Func1<F, Observable<T9>> func) { this.nested = func; } !//больше кода дальше
152
class DoOnNextAsync<F, T> implements Observable.Operator<F, F> { private final Func1<F, Observable<T9>> nested; public DoOnNextAsync(Func1<F, Observable<T9>> func) { this.nested = func; } !//больше кода дальше
153
class DoOnNextAsync<F, T> implements Observable.Operator<F, F> { private final Func1<F, Observable<T9>> nested; public DoOnNextAsync(Func1<F, Observable<T9>> func) { this.nested = func; } !//больше кода дальше
154
@Overridepublic Subscriber<? super F> call(Subscriber<? super F> subscriber) { return new Subscriber<F>() { @Override public void onNext(F next) { if (!subscriber.isUnsubscribed()) { Subscription s = nested.call(next).subscribe(); subscriber.add(s); } }
!//тривиальные onError и onCompleted здесь };}
155
@Overridepublic Subscriber<? super F> call(Subscriber<? super F> subscriber) { return new Subscriber<F>() { @Override public void onNext(F next) { if (!subscriber.isUnsubscribed()) { Subscription s = nested.call(next).subscribe(); subscriber.add(s); } }
!//тривиальные onError и onCompleted здесь };}
156
@Overridepublic Subscriber<? super F> call(Subscriber<? super F> subscriber) { return new Subscriber<F>() { @Override public void onNext(F next) { if (!subscriber.isUnsubscribed()) { Subscription s = nested.call(next).subscribe(); subscriber.add(s); } }
!//тривиальные onError и onCompleted здесь };}
157
@Overridepublic Subscriber<? super F> call(Subscriber<? super F> subscriber) { return new Subscriber<F>() { @Override public void onNext(F next) { if (!subscriber.isUnsubscribed()) { Subscription s = nested.call(next).subscribe(); subscriber.add(s); } }
!//тривиальные onError и onCompleted здесь };}
158
@Overridepublic Subscriber<? super F> call(Subscriber<? super F> subscriber) { return new Subscriber<F>() { @Override public void onNext(F next) { if (!subscriber.isUnsubscribed()) { Subscription s = nested.call(next).subscribe(); subscriber.add(s); } }
!//тривиальные onError и onCompleted здесь };}
• Operator, lift,
• subscribeOn, observeOn
• nest, defer, create
159Понимай, как работает внутри
Думай реактивно
161Думать реактивно
• ñîåäèíÿòü èìïåðàòèâíûé è ðåàêòèâíûé ìèð
161Думать реактивно
• ñîåäèíÿòü èìïåðàòèâíûé è ðåàêòèâíûé ìèð
• íå èñïîëüçîâàòü ñàáäæåêòû
161Думать реактивно
• ñîåäèíÿòü èìïåðàòèâíûé è ðåàêòèâíûé ìèð
• íå èñïîëüçîâàòü ñàáäæåêòû
• îïèñûâàòü, «êàê äåëàòü», à íå «÷òî äåëàòü»
161Думать реактивно
162AutoLoadingRecyclerView
• íàó÷èòü RecycerView àâòîïîäãðóçêå
162AutoLoadingRecyclerView
• íàó÷èòü RecycerView àâòîïîäãðóçêå
• ñäåëàòü ñ ïîìîùüþ RxJava
162AutoLoadingRecyclerView
• íàó÷èòü RecycerView àâòîïîäãðóçêå
• ñäåëàòü ñ ïîìîùüþ RxJava
• â ðåàêòèâíîì ñòèëå
162AutoLoadingRecyclerView
163
public class AutoLoadingRecyclerView<T> extends RecyclerView { protected PublishSubject<Pair<Integer, Integer9>> scrollLoadingChannel = PublishSubject.create(); protected void startScrollingChannel() { addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (shouldLoad()) { int offset = getAdapter().getItemCount(); scrollLoadingChannel.onNext(new Pair(offset, limit)); } } }); }
164
public class AutoLoadingRecyclerView<T> extends RecyclerView { protected PublishSubject<Pair<Integer, Integer9>> scrollLoadingChannel = PublishSubject.create(); protected void startScrollingChannel() { addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (shouldLoad()) { int offset = getAdapter().getItemCount(); scrollLoadingChannel.onNext(new Pair(offset, limit)); } } }); }
165
public class AutoLoadingRecyclerView<T> extends RecyclerView { protected PublishSubject<Pair<Integer, Integer9>> scrollLoadingChannel = PublishSubject.create(); protected void startScrollingChannel() { addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (shouldLoad()) { int offset = getAdapter().getItemCount(); scrollLoadingChannel.onNext(new Pair(offset, limit)); } } }); }
166
public class AutoLoadingRecyclerView<T> extends RecyclerView { protected PublishSubject<Pair<Integer, Integer9>> scrollLoadingChannel = PublishSubject.create(); protected void startScrollingChannel() { addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (shouldLoad()) { int offset = getAdapter().getItemCount(); scrollLoadingChannel.onNext(new Pair(offset, limit)); } } }); }
167
public class AutoLoadingRecyclerView<T> extends RecyclerView { protected PublishSubject<Pair<Integer, Integer9>> scrollLoadingChannel = PublishSubject.create(); protected void startScrollingChannel() { addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (shouldLoad()) { int offset = getAdapter().getItemCount(); scrollLoadingChannel.onNext(new Pair(offset, limit)); } } }); }
168
public class AutoLoadingRecyclerView<T> extends RecyclerView { protected PublishSubject<OffsetAndLimit> scrollLoadingChannel = PublishSubject.create(); protected Subscription loadNewItemsSubscription; protected Subscription subscribeToLoadingChannelSubscription;
169
public class AutoLoadingRecyclerView<T> extends RecyclerView { protected PublishSubject<OffsetAndLimit> scrollLoadingChannel = PublishSubject.create(); protected Subscription loadNewItemsSubscription; protected Subscription subscribeToLoadingChannelSubscription;
170
public class AutoLoadingRecyclerView<T> extends RecyclerView { protected PublishSubject<OffsetAndLimit> scrollLoadingChannel = PublishSubject.create(); protected Subscription loadNewItemsSubscription; protected Subscription subscribeToLoadingChannelSubscription;
171
private static Observable<Integer>getScrollObservable(RecyclerView recyclerView, int limit) { return Observable.create(subscriber 0-> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed() !&& shouldLoad()) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() 0-> recyclerView.removeOnScrollListener(sl))); });}
172
private static Observable<Integer>getScrollObservable(RecyclerView recyclerView, int limit) { return Observable.create(subscriber 0-> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed() !&& shouldLoad()) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() 0-> recyclerView.removeOnScrollListener(sl))); });}
173
private static Observable<Integer>getScrollObservable(RecyclerView recyclerView, int limit) { return Observable.create(subscriber 0-> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed() !&& shouldLoad()) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() 0-> recyclerView.removeOnScrollListener(sl))); });}
174
private static Observable<Integer>getScrollObservable(RecyclerView recyclerView, int limit) { return Observable.create(subscriber 0-> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed() !&& shouldLoad()) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() 0-> recyclerView.removeOnScrollListener(sl))); });}
175
private static Observable<Integer>getScrollObservable(RecyclerView recyclerView, int limit) { return Observable.create(subscriber 0-> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed() !&& shouldLoad()) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() 0-> recyclerView.removeOnScrollListener(sl))); });}
176
private static Observable<Integer>getScrollObservable(RecyclerView recyclerView, int limit) { return Observable.create(subscriber 0-> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed() !&& shouldLoad()) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() 0-> recyclerView.removeOnScrollListener(sl))); });}
177
getScrollObservable() .distinctUntilChanged() .switchMap(offset 0-> getLoadingObservable(offset)) .subscribeOn(Schedulers .from(BackgroundExecutor.getSafeBackgroundExecutor()) ) .observeOn(AndroidSchedulers.mainThread()) .subscribe(loadNewItemsSubscriber);
178
getScrollObservable() .distinctUntilChanged() .switchMap(offset 0-> getLoadingObservable(offset)) .subscribeOn(Schedulers .from(BackgroundExecutor.getSafeBackgroundExecutor()) ) .observeOn(AndroidSchedulers.mainThread()) .subscribe(loadNewItemsSubscriber);
179
getScrollObservable() .distinctUntilChanged() .switchMap(offset 0-> getLoadingObservable(offset)) .subscribeOn(Schedulers .from(BackgroundExecutor.getSafeBackgroundExecutor()) ) .observeOn(AndroidSchedulers.mainThread()) .subscribe(loadNewItemsSubscriber);
180
getScrollObservable() .distinctUntilChanged() .switchMap(offset 0-> getLoadingObservable(offset)) .subscribeOn(Schedulers .from(BackgroundExecutor.getSafeBackgroundExecutor()) ) .observeOn(AndroidSchedulers.mainThread()) .subscribe(loadNewItemsSubscriber);
181
getScrollObservable() .distinctUntilChanged() .switchMap(offset 0-> getLoadingObservable(offset)) .subscribeOn(Schedulers .from(BackgroundExecutor.getSafeBackgroundExecutor()) ) .observeOn(AndroidSchedulers.mainThread()) .subscribe(loadNewItemsSubscriber);
182
getScrollObservable() .distinctUntilChanged() .switchMap(offset 0-> getLoadingObservable(offset)) .subscribeOn(Schedulers .from(BackgroundExecutor.getSafeBackgroundExecutor()) ) .observeOn(AndroidSchedulers.mainThread()) .subscribe(loadNewItemsSubscriber);
183Думать реактивно
• èñïîëüçîâàòü ñòàíäàðòíûå ñðåäñòâà RxJava
183Думать реактивно
• èñïîëüçîâàòü ñòàíäàðòíûå ñðåäñòâà RxJava
• íî íå ñàáäæåêòû
183Думать реактивно
• èñïîëüçîâàòü ñòàíäàðòíûå ñðåäñòâà RxJava
• íî íå ñàáäæåêòû
• ïðè íåîáõîäèìîñòè – ïèñàòü ñâîå
183Думать реактивно
• èñïîëüçîâàòü ñòàíäàðòíûå ñðåäñòâà RxJava
• íî íå ñàáäæåêòû
• ïðè íåîáõîäèìîñòè – ïèñàòü ñâîå
• îáîðà÷èâàòü ñîñòîÿíèå â Observable
183Думать реактивно
• èñïîëüçîâàòü ñòàíäàðòíûå ñðåäñòâà RxJava
• íî íå ñàáäæåêòû
• ïðè íåîáõîäèìîñòè – ïèñàòü ñâîå
• îáîðà÷èâàòü ñîñòîÿíèå â Observable
• ñòàðàòüñÿ è íå ñäàâàòüñÿ
183Думать реактивно
184The Art of Rx
• äóìàòü î
184The Art of Rx
• äóìàòü î
• î êîíòðàêòàõ
184The Art of Rx
• äóìàòü î
• î êîíòðàêòàõ
• î òðåäàõ
184The Art of Rx
• äóìàòü î
• î êîíòðàêòàõ
• î òðåäàõ
• î êîëè÷åñòâå ýëåìåíòîâ
184The Art of Rx
• äóìàòü î
• î êîíòðàêòàõ
• î òðåäàõ
• î êîëè÷åñòâå ýëåìåíòîâ
• äóìàòü êàê
184The Art of Rx
• äóìàòü î
• î êîíòðàêòàõ
• î òðåäàõ
• î êîëè÷åñòâå ýëåìåíòîâ
• äóìàòü êàê
• èíæåíåð
184The Art of Rx
• äóìàòü î
• î êîíòðàêòàõ
• î òðåäàõ
• î êîëè÷åñòâå ýëåìåíòîâ
• äóìàòü êàê
• èíæåíåð
• ðåàêòèâíî
184The Art of Rx
Ñòàòü ïðîôåññèîíàëîì – íåïðîñòî
Íå ëåíèòåñü
Äóìàéòå ìíîãî
Äóìàéòå ìíîãî Î âàæíûõ âåùàõ
189
Ìàëüêîâ Ìàòâåé
Ñïàñèáî
matveyka_jj
Q & A
Top Related