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

MVI approach for android

MVI approach for android

Avatar for PShchahelski

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() }