Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RxPM vs MVP vs MVVM

RxPM vs MVP vs MVVM

Reactive implementation of Presentation Model pattern

Avatar for Dmitriy Gorbunov

Dmitriy Gorbunov

September 12, 2017
Tweet

More Decks by Dmitriy Gorbunov

Other Decks in Programming

Transcript

  1. MVP interface DataView { fun showData(items : List<Item>) fun showProgress()

    fun hideProgress() fun showError(message: String) } 4
  2. MVP class Presenter(private val dataModel: DataModel) { private val composite

    = CompositeDisposable() private var view: DataView? = null init { refreshData() } fun attachView(view: DataView) { this.view = view } fun detachView() { view = null } fun onDestroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { view?.showProgress() } .doAfterTerminate { view?.hideProgress() } .subscribe( { view?.showData(it) }, { view?.showError("Loading data error") } ) .addTo(composite) } } 5
  3. MVP class Presenter(private val dataModel: DataModel) { private val composite

    = CompositeDisposable() private var view: DataView? = null init { refreshData() } fun attachView(view: DataView) { this.view = view } fun detachView() { view = null } fun onDestroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { view?.showProgress() } .doAfterTerminate { view?.hideProgress() } .subscribe( { view?.showData(it) }, { view?.showError("Loading data error") } ) .addTo(composite) } } 6
  4. MVP class Presenter(private val dataModel: DataModel) { private val composite

    = CompositeDisposable() private var view: DataView? = null init { refreshData() } fun attachView(view: DataView) { this.view = view } fun detachView() { view = null } fun onDestroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { view?.showProgress() } .doAfterTerminate { view?.hideProgress() } .subscribe( { view?.showData(it) }, { view?.showError("Loading data error") } ) .addTo(composite) } } 7
  5. MVP class Presenter(private val dataModel: DataModel) { private val composite

    = CompositeDisposable() private var view: DataView? = null init { refreshData() } fun attachView(view: DataView) { this.view = view } fun detachView() { view = null } fun onDestroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { view?.showProgress() } .doAfterTerminate { view?.hideProgress() } .subscribe( { view?.showData(it) }, { view?.showError("Loading data error") } ) .addTo(composite) } } 8
  6. class Presenter(private val dataModel: DataModel) { private val composite =

    CompositeDisposable() private val viewState = DataViewState() init { refreshData() } fun attachView(view: DataView) { viewState.attachView(view) } fun detachView() { viewState.detachView() } fun destroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { viewState.showProgress() } .doAfterTerminate { viewState.hideProgress() } .subscribe( { viewState.showData(it) }, { viewState.showError("Loading data error") } ) .addTo(composite) } } MVP + View State 9
  7. View State class DataViewState : DataView { private var view:

    DataView? = null private var data: List<Item> = emptyList() private var inProgress = false private var error: String? = null fun attachView(view: DataView) { this.view = view view.showData(data) if (inProgress) { view.showProgress() } else { view.hideProgress() } error?.let { view.showError(it) error = null } } fun detachView() { view = null } // ... } 11
  8. View State class DataViewState : DataView { private var view:

    DataView? = null private var data: List<Item> = emptyList() private var inProgress = false private var error: String? = null fun attachView(view: DataView) { this.view = view view.showData(data) if (inProgress) { view.showProgress() } else { view.hideProgress() } error?.let { view.showError(it) error = null } } fun detachView() { view = null } // ... } 12
  9. View State class DataViewState : DataView { private var view:

    DataView? = null private var data: List<Item> = emptyList() private var inProgress = false private var error: String? = null fun attachView(view: DataView) { this.view = view view.showData(data) if (inProgress) { view.showProgress() } else { view.hideProgress() } error?.let { view.showError(it) error = null } } fun detachView() { view = null } // ... } 13
  10. class Presenter(private val dataModel: DataModel) { private val composite =

    CompositeDisposable() private val viewState = DataViewState() init { refreshData() } fun attachView(view: DataView) { viewState.attachView(view) } fun detachView() { viewState.detachView() } fun destroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { viewState.showProgress() } .doAfterTerminate { viewState.hideProgress() } .subscribe( { viewState.showData(it) }, { viewState.showError("Loading data error") } ) .addTo(composite) } } MVP 14
  11. class Presenter(private val dataModel: DataModel) { private val composite =

    CompositeDisposable() private val viewState = DataViewState() init { refreshData() } fun attachView(view: DataView) { viewState.attachView(view) } fun detachView() { viewState.detachView() } fun destroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { viewState.showProgress() } .doAfterTerminate { viewState.hideProgress() } .subscribe( { viewState.showData(it) }, { viewState.showError("Loading data error") } ) .addTo(composite) } } MVP 15
  12. class Presenter(private val dataModel: DataModel) { private val composite =

    CompositeDisposable() private val viewState = DataViewState() init { refreshData() } fun attachView(view: DataView) { viewState.attachView(view) } fun detachView() { viewState.detachView() } fun destroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { viewState.showProgress() } .doAfterTerminate { viewState.hideProgress() } .subscribe( { viewState.showData(it) }, { viewState.showError("Loading data error") } ) .addTo(composite) } } MVP refreshEnabled = !inProgress 16
  13. class Presenter(private val dataModel: DataModel) { private val composite =

    CompositeDisposable() private val viewState = DataViewState() init { refreshData() } fun attachView(view: DataView) { viewState.attachView(view) } fun detachView() { viewState.detachView() } fun destroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { viewState.showProgress() viewState.refreshEnabled(false) } .doAfterTerminate { viewState.hideProgress() viewState.refreshEnabled(true) } .subscribe( { viewState.showData(it) }, { viewState.showError("Loading data error") } ) .addTo(composite) } } MVP 17
  14. class LoginPresenter { private val viewState = ViewState() private var

    username = "" private var password = "" fun userNameChanges(changedUsername: String) { username = changedUsername handleFormChanges() } fun passwordChanges(changedPassword: String) { password = changedPassword handleFormChanges() } private fun handleFormChanges() { viewState.buttonEnabled( enabled = username.isNotEmpty() && password.isNotEmpty() ) } // ... } MVP 19
  15. class LoginPresenter { private val viewState = ViewState() private var

    username = "" private var password = "" fun userNameChanges(changedUsername: String) { username = changedUsername handleFormChanges() } fun passwordChanges(changedPassword: String) { password = changedPassword handleFormChanges() } private fun handleFormChanges() { viewState.buttonEnabled( enabled = username.isNotEmpty() && password.isNotEmpty() ) } // ... } MVP 20
  16. class LoginPresenter { private val viewState = ViewState() private var

    username = "" private var password = "" fun userNameChanges(changedUsername: String) { username = changedUsername handleFormChanges() } fun passwordChanges(changedPassword: String) { password = changedPassword handleFormChanges() } private fun handleFormChanges() { viewState.buttonEnabled( enabled = username.isNotEmpty() && password.isNotEmpty() ) } // ... } MVP 21
  17. class LoginPresenter { private val viewState = ViewState() private var

    username = "" private var password = "" fun userNameChanges(changedUsername: String) { username = changedUsername handleFormChanges() } fun passwordChanges(changedPassword: String) { password = changedPassword handleFormChanges() } private fun handleFormChanges() { viewState.buttonEnabled( enabled = username.isNotEmpty() && password.isNotEmpty() ) } // ... } MVP 22
  18. class LoginPresenter { private val viewState = ViewState() private var

    username = "" private var password = "" fun userNameChanges(changedUsername: String) { username = changedUsername handleFormChanges() } fun passwordChanges(changedPassword: String) { password = changedPassword handleFormChanges() } private fun handleFormChanges() { viewState.buttonEnabled( enabled = username.isNotEmpty() && password.isNotEmpty() ) } // ... } MVP 23
  19. MVP - недостатки • Необходимо писать ViewState • ViewState не

    избавляет от хранения состояния в презентере • Множество callback-ов для реализации связанных состояний • Не совместим с реактивным программированием 24
  20. class PresentationModel(private val dataModel: DataModel) { private val composite =

    CompositeDisposable() var data: List<Item> = emptyList() var inProgress = false var error: String? = null init { refreshData() } fun destroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { inProgress = true } .doAfterTerminate { inProgress = false } .subscribe( { data = it }, { error = "Loading data error" } ) .addTo(composite) } } Presentation Model 26
  21. class PresentationModel(private val dataModel: DataModel) { private val composite =

    CompositeDisposable() var data: List<Item> = emptyList() var inProgress = false var error: String? = null init { refreshData() } fun destroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { inProgress = true } .doAfterTerminate { inProgress = false } .subscribe( { data = it }, { error = "Loading data error" } ) .addTo(composite) } } Presentation Model 27
  22. class ViewModel(private val dataModel: DataModel) { private val composite =

    CompositeDisposable() val data = ObservableArrayList<Item>() val inProgress = ObservableBoolean(false) val error = ObservableField<String?>(null) init { refreshData() } fun destroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { inProgress.set(true) } .doAfterTerminate { inProgress.set(false) } .subscribe( { data.clear(); data.addAll(it) }, { error.set("Loading data error") } ) .addTo(composite) } } MVVM 29
  23. class ViewModel(private val dataModel: DataModel) { private val composite =

    CompositeDisposable() val data = ObservableArrayList<Item>() val inProgress = ObservableBoolean(false) val error = ObservableField<String?>(null) init { refreshData() } fun destroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { inProgress.set(true) } .doAfterTerminate { inProgress.set(false) } .subscribe( { data.clear(); data.addAll(it) }, { error.set("Loading data error") } ) .addTo(composite) } } MVVM 30
  24. <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="vm" type="me.dmdev.rxpm.presentation.mvvm.ViewModel"/> </data>

    <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="@{vm.inProgress ? View.GONE : View.VISIBLE}"/> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:visibility="@{vm.inProgress ? View.VISIBLE : View.GONE}"/> </FrameLayout> </layout> MVVM 31
  25. <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="vm" type="me.dmdev.rxpm.presentation.mvvm.ViewModel"/> </data>

    <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="@{vm.inProgress ? View.GONE : View.VISIBLE}"/> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:visibility="@{vm.inProgress ? View.VISIBLE : View.GONE}"/> </FrameLayout> </layout> MVVM 32
  26. class ViewModel(private val dataModel: DataModel) { private val composite =

    CompositeDisposable() val data = ObservableArrayList<Item>() val inProgress = ObservableBoolean(false) val error = ObservableField<String?>(null) init { refreshData() } fun destroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { inProgress.set(true) } .doAfterTerminate { inProgress.set(false) } .subscribe( { data.clear(); data.addAll(it) }, { error.set("Loading data error") } ) .addTo(composite) } } MVVM 33
  27. class ViewModel(private val dataModel: DataModel) { private val composite =

    CompositeDisposable() val data = ObservableArrayList<Item>() val inProgress = ObservableBoolean(false) val error = ObservableField<String?>(null) init { refreshData() } fun destroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { inProgress.set(true) } .doAfterTerminate { inProgress.set(false) } .subscribe( { data.clear(); data.addAll(it) }, { error.set("Loading data error") } ) .addTo(composite) } } MVVM Если мы хотим показать алерт? 34
  28. class ViewModel(private val dataModel: DataModel, private val callbacks: Callbacks) {

    private val composite = CompositeDisposable() val data = ObservableArrayList<Item>() val inProgress = ObservableBoolean(false) init { refreshData() } fun destroy() { composite.clear() } fun refreshData() { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { inProgress.set(true) } .doAfterTerminate { inProgress.set(false) } .subscribe( { data.clear(); data.addAll(it) }, { callbacks.showError("Loading data error") } ) } } MVVM 35
  29. MVVM - недостатки • XML - инструмент для верстки, а

    не для кода • Сложные вещи все равно приходится делать в коде • Не совместим с реактивным программированием 36
  30. class ReactivePresentationModel(private val dataModel: DataModel) { private val composite =

    CompositeDisposable() val data = BehaviorRelay.createDefault<List<Item>>(emptyList())!! val inProgress = BehaviorRelay.createDefault(false)!! val error = PublishRelay.create<String>()!! val refreshDataAction = PublishRelay.create<Unit>()!! init { refreshDataAction .filter { inProgress.value != true } .flatMapSingle { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { inProgress.accept(true) } .doAfterTerminate { inProgress.accept(false) } .doOnError { error.accept("Loading data error") } } .retry() .subscribe(data) .addTo(composite) refreshDataAction.accept(Unit) } fun destroy() { composite.clear() } } RxPM 44
  31. class ReactivePresentationModel(private val dataModel: DataModel) { private val composite =

    CompositeDisposable() val data = BehaviorRelay.createDefault<List<Item>>(emptyList())!! val inProgress = BehaviorRelay.createDefault(false)!! val error = PublishRelay.create<String>()!! val refreshDataAction = PublishRelay.create<Unit>()!! init { refreshDataAction .filter { inProgress.value != true } .flatMapSingle { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { inProgress.accept(true) } .doAfterTerminate { inProgress.accept(false) } .doOnError { error.accept("Loading data error") } } .retry() .subscribe(data) .addTo(composite) refreshDataAction.accept(Unit) } fun destroy() { composite.clear() } } RxPM 45
  32. private val data = BehaviorRelay.createDefault<List<Item>>(emptyList()) private val inProgress = BehaviorRelay.createDefault(false)

    private val error = PublishRelay.create<String>() private val refreshAction = PublishRelay.create<Unit>() RxPM 46
  33. private val data = BehaviorRelay.createDefault<List<Item>>(emptyList()) val dataState: Observable<List<Item>> = data.hide()!!

    private val inProgress = BehaviorRelay.createDefault(false) val inProgressState: Observable<Boolean> = inProgress.hide()!! private val error = PublishRelay.create<String>() val errorObservable: Observable<String> = error.hide()!! private val refreshAction = PublishRelay.create<Unit>() val refreshActionConsumer : Consumer<Unit> = refreshAction RxPM 47
  34. private val data = BehaviorRelay.createDefault<List<Item>>(emptyList()) val dataState: Observable<List<Item>> = data.hide()!!

    private val inProgress = BehaviorRelay.createDefault(false) val inProgressState: Observable<Boolean> = inProgress.hide()!! private val error = PublishRelay.create<String>() val errorObservable: Observable<String> = error.hide()!! private val refreshAction = PublishRelay.create<Unit>() val refreshActionConsumer : Consumer<Unit> = refreshAction inline fun <T> Relay<T>.asObservable(): Observable<T> {
 return this.hide()
 }
 
 inline fun <T> Relay<T>.asConsumer(): Consumer<T> {
 return this
 } RxPM 48
  35. private val data = BehaviorRelay.createDefault<List<Item>>(emptyList()) val dataState = data.asObservable() private

    val inProgress = BehaviorRelay.createDefault(false) val inProgressState = inProgress.asObservable() private val error = PublishRelay.create<String>() val errorObservable = error.asObservable() private val refreshAction = PublishRelay.create<Unit>() val refreshActionConsumer = refreshAction.asConsumer() RxPM 49
  36. val composite = CompositeDisposable() pm.dataState .subscribe { adapter.items = it

    } .addTo(composite) pm.inProgressState .subscribe(progressBar.visibility()) .addTo(composite) pm.errorObservable .subscribe { // show alert dialog } .addTo(composite) refreshButton.clicks() .subscribe(pm.refreshActionConsumer) .addTo(composite) RxPM https://github.com/JakeWharton/RxBinding 50
  37. RxPM - library https://github.com/dmdevgo/RxPM /** * @author Dmitriy Gorbunov (dmdev)

    * @author Vasili Chyrvon (Jeevuz) */ https://t.me/Rx_PM 51
  38. inner class State<T>(initialValue: T? = null) {
 internal val relay

    // init relay
 val observable = relay.asObservable()
 val value: T? get() = relay.value
 fun hasValue() = relay.hasValue()
 } RxPM - State 52
  39. inner class State<T>(initialValue: T? = null) {
 internal val relay

    // init relay
 val observable = relay.asObservable()
 val value: T? get() = relay.value
 fun hasValue() = relay.hasValue()
 } // открываем доступ в Presentation Model protected val <T> State<T>.consumer: Consumer<T> get() = relay RxPM - State 53
  40. inner class State<T>(initialValue: T? = null) {
 internal val relay

    // init relay
 val observable = relay.asObservable()
 val value: T? get() = relay.value
 fun hasValue() = relay.hasValue()
 } // открываем доступ в Presentation Model protected val <T> State<T>.consumer: Consumer<T> get() = relay // объявляем в Presentation Model val inProgress = State<Boolean>(false) RxPM - State 54
  41. inner class State<T>(initialValue: T? = null) {
 internal val relay

    // init relay
 val observable = relay.asObservable()
 val value: T? get() = relay.value
 fun hasValue() = relay.hasValue()
 } // открываем доступ в Presentation Model protected val <T> State<T>.consumer: Consumer<T> get() = relay // объявляем в Presentation Model val inProgress = State<Boolean>(false) // consumer можно получить только в PM
 inProgress.consumer RxPM - State 55
  42. inner class State<T>(initialValue: T? = null) {
 internal val relay

    // init relay
 val observable = relay.asObservable()
 val value: T? get() = relay.value
 fun hasValue() = relay.hasValue()
 } // открываем доступ в Presentation Model protected val <T> State<T>.consumer: Consumer<T> get() = relay // объявляем в Presentation Model val inProgress = State<Boolean>(false) // consumer можно получить только в PM
 inProgress.consumer // во View pm.inProgress.observable.bindTo(progressBar.visibility()) RxPM - State 56
  43. inner class Action<T> {
 internal val relay = PublishRelay.create<T>()
 val

    consumer: Consumer<T> = relay
 } RxPM - Action 57
  44. inner class Action<T> {
 internal val relay = PublishRelay.create<T>()
 val

    consumer: Consumer<T> = relay
 } // открываем доступ в Presentation Model protected val <T> Action<T>.observable: Observable<T> get() = relay RxPM - Action 58
  45. inner class Action<T> {
 internal val relay = PublishRelay.create<T>()
 val

    consumer: Consumer<T> = relay
 } // открываем доступ в Presentation Model protected val <T> Action<T>.observable: Observable<T> get() = relay // объявляем в Presentation Model val clicks = Action<Unit>() RxPM - Action 59
  46. inner class Action<T> {
 internal val relay = PublishRelay.create<T>()
 val

    consumer: Consumer<T> = relay
 } // открываем доступ в Presentation Model protected val <T> Action<T>.observable: Observable<T> get() = relay // объявляем в Presentation Model val clicks = Action<Unit>() // observable можно получить только в PM
 clicks.observable RxPM - Action 60
  47. inner class Action<T> {
 internal val relay = PublishRelay.create<T>()
 val

    consumer: Consumer<T> = relay
 } // открываем доступ в Presentation Model protected val <T> Action<T>.observable: Observable<T> get() = relay // объявляем в Presentation Model val clicks = Action<Unit>() // observable можно получить только в PM
 clicks.observable // во View button.clicks().bindTo(pm.clicks.consumer) RxPM - Action 61
  48. inner class Command<T>(isIdle: Observable<Boolean>? = null,
 bufferSize: Int? = null)

    {
 internal val relay = PublishRelay.create<T>()
 val observable =
 if (isIdle == null) relay.bufferWhileUnbind(bufferSize)
 else relay.bufferWhileIdle(isIdle, bufferSize)
 } RxPM - Command 62
  49. inner class Command<T>(isIdle: Observable<Boolean>? = null,
 bufferSize: Int? = null)

    {
 internal val relay = PublishRelay.create<T>()
 val observable =
 if (isIdle == null) relay.bufferWhileUnbind(bufferSize)
 else relay.bufferWhileIdle(isIdle, bufferSize)
 } // открываем доступ в Presentation Model protected val <T> Command<T>.consumer: Consumer<T> get() = relay RxPM - Command 63
  50. inner class Command<T>(isIdle: Observable<Boolean>? = null,
 bufferSize: Int? = null)

    {
 internal val relay = PublishRelay.create<T>()
 val observable =
 if (isIdle == null) relay.bufferWhileUnbind(bufferSize)
 else relay.bufferWhileIdle(isIdle, bufferSize)
 } // открываем доступ в Presentation Model protected val <T> Command<T>.consumer: Consumer<T> get() = relay // объявляем в Presentation Model val showError = Command<String>(bufferSize = 1) RxPM - Command 64
  51. inner class Command<T>(isIdle: Observable<Boolean>? = null,
 bufferSize: Int? = null)

    {
 internal val relay = PublishRelay.create<T>()
 val observable =
 if (isIdle == null) relay.bufferWhileUnbind(bufferSize)
 else relay.bufferWhileIdle(isIdle, bufferSize)
 } // открываем доступ в Presentation Model protected val <T> Command<T>.consumer: Consumer<T> get() = relay // объявляем в Presentation Model val showError = Command<String>(bufferSize = 1) // consumer можно получить только в PM
 showError.consumer RxPM - Command 65
  52. inner class Command<T>(isIdle: Observable<Boolean>? = null,
 bufferSize: Int? = null)

    {
 internal val relay = PublishRelay.create<T>()
 val observable =
 if (isIdle == null) relay.bufferWhileUnbind(bufferSize)
 else relay.bufferWhileIdle(isIdle, bufferSize)
 } // открываем доступ в Presentation Model protected val <T> Command<T>.consumer: Consumer<T> get() = relay // объявляем в Presentation Model val showError = Command<String>(bufferSize = 1) // consumer можно получить только в PM
 showError.consumer // во View pm.showError.observable.bindTo { // show alert dialog } RxPM - Command 66
  53. class ReactivePresentationModel(private val dataModel: DataModel) : PresentationModel() { val data

    = State<List<Item>>(emptyList()) val inProgress = State(false) val error = Command<String>() val refreshAction = Action<Unit>() override fun onCreate() { super.onCreate() refreshAction.observable .skipWhileInProgress(inProgress.observable) .flatMapSingle { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .bindProgress(inProgress.consumer) .doOnError { error.consumer.accept("Loading data error") } } .retry() .subscribe(data.consumer) .untilDestroy() refreshAction.consumer.accept(Unit) } } RxPM - PresentationModel 69
  54. class ReactivePresentationModel(private val dataModel: DataModel) : PresentationModel() { val data

    = State<List<Item>>(emptyList()) val inProgress = State(false) val error = Command<String>() val refreshAction = Action<Unit>() override fun onCreate() { super.onCreate() refreshAction.observable .skipWhileInProgress(inProgress.observable) .flatMapSingle { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .bindProgress(inProgress.consumer) .doOnError { error.consumer.accept("Loading data error") } } .retry() .subscribe(data.consumer) .untilDestroy() refreshAction.consumer.accept(Unit) } } RxPM - PresentationModel 70
  55. class ReactivePresentationModel(private val dataModel: DataModel) : PresentationModel() { val data

    = State<List<Item>>(emptyList()) val inProgress = State(false) val error = Command<String>() val refreshAction = Action<Unit>() override fun onCreate() { super.onCreate() refreshAction.observable .skipWhileInProgress(inProgress.observable) .flatMapSingle { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .bindProgress(inProgress.consumer) .doOnError { error.consumer.accept("Loading data error") } } .retry() .subscribe(data.consumer) .untilDestroy() refreshAction.consumer.accept(Unit) } } RxPM - PresentationModel 71
  56. class ReactivePresentationModel(private val dataModel: DataModel) : PresentationModel() { val data

    = State<List<Item>>(emptyList()) val inProgress = State(false) val error = Command<String>() val refreshAction = Action<Unit>() override fun onCreate() { super.onCreate() refreshAction.observable .skipWhileInProgress(inProgress.observable) .flatMapSingle { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .bindProgress(inProgress.consumer) .doOnError { error.consumer.accept("Loading data error") } } .retry() .subscribe(data.consumer) .untilDestroy() refreshAction.consumer.accept(Unit) } } RxPM - PresentationModel 72
  57. infix fun <T> Observable<T>.bindTo(consumer: Consumer<in T>) {
 compositeUnbind.add(
 this
 .observeOn(AndroidSchedulers.mainThread())


    .subscribe(consumer)
 )
 }
 
 infix fun <T> Observable<T>.bindTo(consumer: (T) -> Unit) {
 compositeUnbind.add(
 this
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(consumer)
 )
 } RxPM - bindTo 73
  58. class DataFragment : PmSupportFragment<DataPresentationModel>() { override fun providePresentationModel() = DataPresentationModel(DataModel())

    // onCreateView() override fun onBindPresentationModel(pm: DataPresentationModel) { pm.inProgress.observable.bindTo(progressBar.visibility()) pm.data.observable.bindTo { // adapter.setItems(it) } pm.error.observable.bindTo { // show alert dialog } refreshButton.clicks().bindTo(pm.refreshAction.consumer) } } RxPM - View 74
  59. class LoginPresentationModel : PresentationModel() { val name = inputControl() val

    password = inputControl() val buttonEnabled = State<Boolean>() override fun onCreate() { super.onCreate() Observable.combineLatest(name.text.observable, password.text.observable, BiFunction { name: String, password: String -> name.isNotEmpty() && password.isNotEmpty() }) .subscribe(buttonEnabled.consumer) .untilDestroy() } } RxPM - InputControl 84
  60. class LoginPresentationModel : PresentationModel() { val name = inputControl() val

    password = inputControl() val buttonEnabled = State<Boolean>() override fun onCreate() { super.onCreate() Observable.combineLatest(name.text.observable, password.text.observable, BiFunction { name: String, password: String -> name.isNotEmpty() && password.isNotEmpty() }) .subscribe(buttonEnabled.consumer) .untilDestroy() } } RxPM - InputControl 85
  61. class LoginPresentationModel : PresentationModel() { val name = inputControl() val

    password = inputControl() val buttonEnabled = State<Boolean>() override fun onCreate() { super.onCreate() Observable.combineLatest(name.text.observable, password.text.observable, BiFunction { name: String, password: String -> name.isNotEmpty() && password.isNotEmpty() }) .subscribe(buttonEnabled.consumer) .untilDestroy() } } RxPM - InputControl 86
  62. class LoginPresentationModel : PresentationModel() { val name = inputControl() val

    password = inputControl() val buttonEnabled = State<Boolean>() override fun onCreate() { super.onCreate() Observable.combineLatest(name.text.observable, password.text.observable, BiFunction { name: String, password: String -> name.isNotEmpty() && password.isNotEmpty() }) .subscribe(buttonEnabled.consumer) .untilDestroy() } } override fun onBindPresentationModel(pm: LoginPresentationModel) { pm.name bindTo nameEditText pm.password bindTo passwordEditText pm.buttonEnabled.observable bindTo button.enabled() } RxPM - InputControl 87
  63. class LoginPresentationModel : PresentationModel() { val name = inputControl( formatter

    = { it.take(50).capitalize().replace("[^a-zA-Z- ]".toRegex(), "") } ) val password = inputControl() val buttonEnabled = State<Boolean>() override fun onCreate() { super.onCreate() Observable.combineLatest(name.text.observable, password.text.observable, BiFunction { name: String, password: String -> name.isNotEmpty() && password.isNotEmpty() }) .subscribe(buttonEnabled.consumer) .untilDestroy() } } RxPM - InputControl 88
  64. RxPM - library • Базовая реализация PresentationModel • Сохранение PresentationModel

    во время поворота экрана • Базовые классы для реализации View • State, Action, Command • InputControl, CheckContol, ClickControl • Связывание свойств через bindTo и другие полезные расширения • Базовые классы для работы с Google Maps 93
  65. Dmitriy Gorbunov contacts { telegram = @dmdev github = @dmdevgo

    email = [email protected] } links { https://github.com/dmdevgo/RxPM https://github.com/Jeevuz/Outlast https://habrahabr.ru/company/mobileup/blog/326962/ https://habrahabr.ru/company/mobileup/blog/313538/ https://habrahabr.ru/company/mobileup/blog/312548/ https://martinfowler.com/eaaDev/PresentationModel.html https://github.com/JakeWharton/RxRelay https://github.com/JakeWharton/RxBinding }