Slide 1

Slide 1 text

YET ANOTHER M… TOPIC PAVEL SHCHAHELSKI

Slide 2

Slide 2 text

DISCLAIMER

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

CONTEXT

Slide 5

Slide 5 text

MACHINE OVERVIEW

Slide 6

Slide 6 text

MODEL VIEW INTENT

Slide 7

Slide 7 text

user()

Slide 8

Slide 8 text

intent(user())

Slide 9

Slide 9 text

model(intent(user()))

Slide 10

Slide 10 text

view(model(intent(user())))

Slide 11

Slide 11 text

user(view(model(intent(user()))))

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

UI

Slide 14

Slide 14 text

intent(user())

Slide 15

Slide 15 text

MVI INTENT sealed class BrowseIntent : MviBaseIntent { } interface MviBaseIntent

Slide 16

Slide 16 text

sealed class BrowseIntent : MviBaseIntent { object InitialIntent : BrowseIntent() object RefreshIntent : BrowseIntent() } MVI INTENT

Slide 17

Slide 17 text

MVI INTENT override fun intents(): Observable { return initialIntent() }

Slide 18

Slide 18 text

MVI INTENT private fun initialIntent(): Observable { return Observable.just(BrowseIntent.InitialIntent) } override fun intents(): Observable { return initialIntent() }

Slide 19

Slide 19 text

MVI INTENT override fun intents(): Observable { return refreshIntent() } private fun initialIntent(): Observable { return Observable.just(BrowseIntent.InitialIntent) } override fun intents(): Observable { return initialIntent() } private fun refreshIntent(): Observable { return RxSwipeRefreshLayout.refreshes(refresh_layout) .map { BrowseIntent.RefreshIntent } }

Slide 20

Slide 20 text

MVI INTENT private fun initialIntent(): Observable { return Observable.just(BrowseIntent.InitialIntent) } private fun refreshIntent(): Observable { return RxSwipeRefreshLayout.refreshes(refresh_layout) .map { BrowseIntent.RefreshIntent } } override fun intents(): Observable { return Observable.merge(initialIntent(), refreshIntent()) }

Slide 21

Slide 21 text

MVI INTENT INITIAL INTENT MERGE INTENTS() REFRESH INTENT

Slide 22

Slide 22 text

MODEL

Slide 23

Slide 23 text

model(intent(user()))

Slide 24

Slide 24 text

MVI MODEL intents //Observable .map(this::actionFromIntent) //Observable

Slide 25

Slide 25 text

MVI MODEL private fun actionFromIntent(intent: MviBaseIntent): BrowseAction { return when (intent) { is BrowseIntent.RefreshIntent -> BrowseAction.RefreshAction is BrowseIntent.InitialIntent -> BrowseAction.LoadAction else -> throw UnsupportedOperationException("Oops, that looks like an unknown intent: " + intent) } }

Slide 26

Slide 26 text

MVI MODEL interface MviBaseAction sealed class BrowseAction : MviBaseAction { object LoadAction : BrowseAction() object RefreshAction : BrowseAction() }

Slide 27

Slide 27 text

MVI MODEL MAP TO ACTIONS ACTIONS() INTENTS

Slide 28

Slide 28 text

DATA

Slide 29

Slide 29 text

MVI DATA var actionProcessor: ObservableTransformer = ObservableTransformer { actions -> }

Slide 30

Slide 30 text

MVI DATA var actionProcessor: ObservableTransformer = ObservableTransformer { actions -> actions.publish { shared -> } }

Slide 31

Slide 31 text

MVI DATA var actionProcessor: ObservableTransformer = ObservableTransformer { actions -> actions.publish { shared -> Observable.merge( shared.ofType(LoadAction::class.java).compose(loadProcessor), shared.ofType(RefreshAction::class.java).compose(refreshProcessor) ) } }

Slide 32

Slide 32 text

MVI DATA PUBLISH RESULTS() MERGE ACTIONS ACTION #1 ACTION #2 ACTION #N

Slide 33

Slide 33 text

MVI DATA val loadProcessor: ObservableTransformer = ObservableTransformer { actions -> actions.flatMap { getContactList.execute() } }

Slide 34

Slide 34 text

MVI DATA val loadProcessor: ObservableTransformer = ObservableTransformer { actions -> actions.flatMap { getContactList.execute() .map { list -> LoadTasksResult.Success(list) } } }

Slide 35

Slide 35 text

MVI DATA val loadProcessor: ObservableTransformer = ObservableTransformer { actions -> actions.flatMap { getContactList.execute() .map { list -> LoadTasksResult.Success(list) } .startWith(LoadTasksResult.InFlight) .onErrorReturn(LoadTasksResult::Failure) } }

Slide 36

Slide 36 text

MVI DATA sealed class LoadTasksResult : BrowseResult() { data class Success(val contacts: List) : LoadTasksResult() data class Failure(val error: Throwable) : LoadTasksResult() object InFlight : LoadTasksResult() }

Slide 37

Slide 37 text

MVI intents //Observable .map(this::actionFromIntent) //Observable .compose(processor.actionProcessor) //Observable DATA

Slide 38

Slide 38 text

MVI DATA MAP INTENTS PROCESSOR ACTIONS RESULTS

Slide 39

Slide 39 text

VIEW

Slide 40

Slide 40 text

view(model(intent(user())))

Slide 41

Slide 41 text

MVI interface MviBaseViewState data class BrowseUiViewState : MviBaseViewState VIEW

Slide 42

Slide 42 text

MVI data class BrowseUiViewState(val isLoading: Boolean, val isRefreshing: Boolean, val contacts: List, val error: Throwable?) : MviBaseViewState VIEW

Slide 43

Slide 43 text

MVI val reducer: BiFunction = BiFunction { previousState, result -> } VIEW

Slide 44

Slide 44 text

MVI VIEW intents //Observable .map(this::actionFromIntent) //Observable .compose(processor.actionProcessor) //Observable .scan(BrowseUiViewState.idle(), reducer) //Observable

Slide 45

Slide 45 text

MVI val reducer: BiFunction = BiFunction { previousState, result -> when (result) { is LoadTasksResult -> { } is RefreshTasksResult -> { } } } VIEW

Slide 46

Slide 46 text

MVI val reducer: BiFunction = BiFunction { previousState, result -> when (result) { is LoadTasksResult -> { when (result) { is Success -> previousState.copy(isLoading = false, contacts = result.contacts.map { mapper.mapToDisplayObject(it) }) is Failure -> previousState.copy(isLoading = false, error = result.error) is InFlight -> previousState.copy(isLoading = true) } } } } VIEW

Slide 47

Slide 47 text

MVI DATA MAP INTENTS PROCESSOR ACTIONS RESULTS REDUCER UISTATE

Slide 48

Slide 48 text

MVI VIEW intents //Observable .map(this::actionFromIntent) //Observable .compose(processor.actionProcessor) //Observable .scan(BrowseUiViewState.idle(), reducer) //Observable .subscribe { state -> render(state) }

Slide 49

Slide 49 text

MVI VIEW override fun render(state: BrowseUiViewState) { refresh_layout.isRefreshing = state.isRefreshing }

Slide 50

Slide 50 text

MVI VIEW override fun render(state: BrowseUiViewState) { refresh_layout.isRefreshing = state.isRefreshing if (state.isLoading) { progress.visibility = View.VISIBLE list.visibility = View.GONE } }

Slide 51

Slide 51 text

MVI VIEW override fun render(state: BrowseUiViewState) { refresh_layout.isRefreshing = state.isRefreshing if (state.isLoading) { progress.visibility = View.VISIBLE list.visibility = View.GONE } val contacts = state.contacts if (contacts != null) { progress.visibility = View.GONE if (contacts.isNotEmpty()) { browseAdapter.apply { this.contacts = contacts notifyDataSetChanged() } list.visibility = View.VISIBLE } } }

Slide 52

Slide 52 text

USER INTERFACE Intents

Slide 53

Slide 53 text

USER INTERFACE Intents VIEW MODEL Intent Processor To Actions Processor To Result Reducer To State

Slide 54

Slide 54 text

USER INTERFACE Render USER INTERFACE Intents VIEW MODEL Intent Processor To Actions Processor To Result Reducer To State

Slide 55

Slide 55 text

MVI IMPLEMENTATION interface MviBaseView { fun intents(): Observable fun render(state: S) } interface MviBaseViewModel { fun processIntents(intents: Observable) fun states(): Observable }

Slide 56

Slide 56 text

MVI IMPLEMENTATION interface MviBaseView { fun intents(): Observable fun render(state: S) } interface MviBaseViewModel { fun processIntents(intents: Observable) fun states(): Observable }

Slide 57

Slide 57 text

MVI IMPLEMENTATION interface MviBaseView { fun intents(): Observable fun render(state: S) } interface MviBaseViewModel { fun processIntents(intents: Observable) fun states(): Observable }

Slide 58

Slide 58 text

CONFIG CHANGES

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

MVI VIEW MODEL class BrowseContactListViewModel @Inject internal constructor( private val processor: BrowseListProcessor, private val mapper: ContactMapper) : ViewModel(), MviBaseViewModel { override fun processIntents(intents: Observable) { } override fun states(): Observable { } }

Slide 61

Slide 61 text

MVI VIEW MODEL class BrowseContactListViewModel @Inject internal constructor() : ViewModel(), MviBaseViewModel { private var intentsSubject: PublishSubject = PublishSubject.create() override fun processIntents(intents: Observable) { intents.subscribe(intentsSubject) } override fun states(): Observable { } }

Slide 62

Slide 62 text

MVI VIEW MODEL class BrowseContactListViewModel @Inject internal constructor() : ViewModel(), MviBaseViewModel { private var intentsSubject: PublishSubject = PublishSubject.create() private val statesSubject: BehaviorSubject = BehaviorSubject.create() override fun processIntents(intents: Observable) { intents.subscribe(intentsSubject) } override fun states(): Observable { return statesSubject } }

Slide 63

Slide 63 text

MVI VIEW MODEL class BrowseContactListViewModel @Inject internal constructor() : ViewModel(), MviBaseViewModel { private var intentsSubject: PublishSubject = PublishSubject.create() private val statesSubject: BehaviorSubject = BehaviorSubject.create() init { intentsSubject .map(this::actionFromIntent) .compose(processor.actionProcessor) .scan(BrowseUiViewState.idle(), reducer) .subscribe(statesSubject) } override fun processIntents(intents: Observable) { intents.subscribe(intentsSubject) } override fun states(): Observable { return statesSubject } }

Slide 64

Slide 64 text

MVI FRAGMENT override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.a_main) viewModel = ViewModelProviders.of(this, viewModelFactory) .get(BrowseContactListViewModel::class.java) compositeDisposable += viewModel.states().subscribe(this::render) viewModel.processIntents(intents()) } override fun onDestroy() { compositeDisposable.dispose() super.onDestroy() }

Slide 65

Slide 65 text

MVI FRAGMENT override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.a_main) viewModel = ViewModelProviders.of(this, viewModelFactory) .get(BrowseContactListViewModel::class.java) compositeDisposable += viewModel.states().subscribe(this::render) viewModel.processIntents(intents()) } override fun onDestroy() { compositeDisposable.dispose() super.onDestroy() }

Slide 66

Slide 66 text

PShchahelski/MviTestProject

Slide 67

Slide 67 text

PROS & CONS

Slide 68

Slide 68 text

THANK YOU QA?

Slide 69

Slide 69 text

SUMMARY