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

MVI approach for android

MVI approach for android

PShchahelski

February 26, 2018
Tweet

More Decks by PShchahelski

Other Decks in Programming

Transcript

  1. UI

  2. MVI INTENT override fun intents(): Observable<BrowseIntent> { return refreshIntent() }

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

    private fun refreshIntent(): Observable<BrowseIntent.RefreshIntent> { return RxSwipeRefreshLayout.refreshes(refresh_layout) .map { BrowseIntent.RefreshIntent } } override fun intents(): Observable<BrowseIntent> { return Observable.merge(initialIntent(), refreshIntent()) }
  4. 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) } }
  5. MVI MODEL interface MviBaseAction sealed class BrowseAction : MviBaseAction {

    object LoadAction : BrowseAction() object RefreshAction : BrowseAction() }
  6. MVI DATA var actionProcessor: ObservableTransformer<BrowseAction, BrowseResult> = ObservableTransformer { actions

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

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

    -> actions.flatMap { getContactList.execute() .map { list -> LoadTasksResult.Success(list) } .startWith(LoadTasksResult.InFlight) .onErrorReturn(LoadTasksResult::Failure) } }
  9. MVI DATA sealed class LoadTasksResult : BrowseResult() { data class

    Success(val contacts: List<Contact>) : LoadTasksResult() data class Failure(val error: Throwable) : LoadTasksResult() object InFlight : LoadTasksResult() }
  10. MVI data class BrowseUiViewState(val isLoading: Boolean, val isRefreshing: Boolean, val

    contacts: List<ContactDisplayObject>, val error: Throwable?) : MviBaseViewState VIEW
  11. MVI val reducer: BiFunction<BrowseUiViewState, BrowseResult, BrowseUiViewState> = BiFunction { previousState,

    result -> when (result) { is LoadTasksResult -> { } is RefreshTasksResult -> { } } } VIEW
  12. MVI val reducer: BiFunction<BrowseUiViewState, BrowseResult, BrowseUiViewState> = 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
  13. MVI VIEW override fun render(state: BrowseUiViewState) { refresh_layout.isRefreshing = state.isRefreshing

    if (state.isLoading) { progress.visibility = View.VISIBLE list.visibility = View.GONE } }
  14. 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 } } }
  15. USER INTERFACE Render USER INTERFACE Intents VIEW MODEL Intent Processor

    To Actions Processor To Result Reducer To State
  16. MVI IMPLEMENTATION interface MviBaseView<I : MviBaseIntent, in S : MviBaseViewState>

    { fun intents(): Observable<I> fun render(state: S) } interface MviBaseViewModel<I : MviBaseIntent, S : MviBaseViewState> { fun processIntents(intents: Observable<I>) fun states(): Observable<S> }
  17. MVI IMPLEMENTATION interface MviBaseView<I : MviBaseIntent, in S : MviBaseViewState>

    { fun intents(): Observable<I> fun render(state: S) } interface MviBaseViewModel<I : MviBaseIntent, S : MviBaseViewState> { fun processIntents(intents: Observable<I>) fun states(): Observable<S> }
  18. MVI IMPLEMENTATION interface MviBaseView<I : MviBaseIntent, in S : MviBaseViewState>

    { fun intents(): Observable<I> fun render(state: S) } interface MviBaseViewModel<I : MviBaseIntent, S : MviBaseViewState> { fun processIntents(intents: Observable<I>) fun states(): Observable<S> }
  19. MVI VIEW MODEL class BrowseContactListViewModel @Inject internal constructor( private val

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

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

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

    MviBaseViewModel<BrowseIntent, BrowseUiViewState> { private var intentsSubject: PublishSubject<BrowseIntent> = PublishSubject.create() private val statesSubject: BehaviorSubject<BrowseUiViewState> = BehaviorSubject.create() init { intentsSubject .map(this::actionFromIntent) .compose(processor.actionProcessor) .scan<BrowseUiViewState>(BrowseUiViewState.idle(), reducer) .subscribe(statesSubject) } override fun processIntents(intents: Observable<BrowseIntent>) { intents.subscribe(intentsSubject) } override fun states(): Observable<BrowseUiViewState> { return statesSubject } }
  23. 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() }
  24. 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() }