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

Women Who Code Meetup: Most Valuable (Architect...

Aida Issayeva
December 14, 2021

Women Who Code Meetup: Most Valuable (Architecture) Idea

PRESENTED AT:
Women Who Code Meetup

DATE:
December 14, 2021

DESCRIPTION:
Unlock the potential of the Model-View-Intent (MVI) architecture pattern in Android development. In this talk, delve into the core concepts of MVI and discover how it revolutionizes app architecture, enabling cleaner, more maintainable, and highly testable code. Join us to explore real-world use cases, best practices, and the value MVI brings to your Android projects.

MORE TALKS & ARTICLES FROM ME: https://cupsofcode.com/talks/

Aida Issayeva

December 14, 2021
Tweet

More Decks by Aida Issayeva

Other Decks in Technology

Transcript

  1. About me 󰠁 Software Engineer 🤖 I build Android apps

    󰠅 Udacity Instructor 🐦 @aida_isay 🌐 cupsofcode.com
  2. Current State of Affairs in Android App 1. Many Asynchronous

    Executions; 2. Updates to the states from random places;
  3. Current State of Affairs in Android App 1. Many Asynchronous

    Executions; 2. Updates to the states from random places; 3. Different States in Widgets, Fragments/Activities, ViewModels, Domain and Data Layers, etc;
  4. Model as a State data class ViewState( val isLoading: Boolean

    = true, val error: Throwable? = null, val itemName: String = "", val totalPrice: Int = 0, val isButtonEnabled: Boolean = false )
  5. Render Model to View private fun render(context: Context) { mviViewModel.bindIntents(intents.hide())

    .subscribe({ viewstate -> binding.loadingBar.isVisible = viewstate.isLoading switchError(viewstate.error) binding.button.isEnabled = viewstate.isButtonEnabled binding.itemName.text = viewstate.itemName …. }, { …. }) }
  6. Intents are passed to VM private fun render(context: Context) {

    mviViewModel.bindIntents(intents.hide()) .subscribe({ viewstate -> …. }, { …. }) } binding.button.setOnClickListener { intents.onNext(MVIView.Intent.ButtonClicked) }
  7. Intent is an Action / Event / Input / etc

    sealed class Intent { // data data class Data(val maxNumberInDropdown: Int, val itemName: String, val itemPrice: Int) : Intent() data class Error(val throwable: Throwable) : Intent() object Loading : Intent() // user actions data class DropdownNumberSelected(val number: Int) : Intent() object ButtonClicked : Intent() object OnBackClicked : Intent() object DialogDismissed: Intent() }
  8. Processing Intents: Transformation private fun bindIntent(viewIntents: Observable<Intent>): Observable<Intent> { val

    transformedIntents = viewIntents.publish { val buttonClicked = it.ofType(ButtonClicked::class.java) .debounce(300, TimeUnit.MILLISECONDS) .switchMap { ThrowErrorUseCase.execute() .map<Intent> { NoOp } .onErrorReturn { Error(throwable = it) } .startWith(Loading) } …. Observable.merge( buttonClicked, … ) } return Observable.merge(transformedIntents, … , …) }
  9. Processing Intents: Data private fun bindIntent(viewIntents: Observable<Intent>): Observable<Intent> { ….

    val dataIntent = GetItemUseCase.execute() .map<Intent> { Data( itemPrice = it.price.toInt(), itemName = it.name, maxNumberInDropdown = it.maxCount ) } .onErrorReturn { Error(throwable = it) } .startWith(Loading) return Observable.merge(..., dataIntent, …) }
  10. Reducing Intent to State fun bindIntents(viewIntents: Observable<Intent>): Observable<ViewState> { return

    bindIntent(viewIntents) .subscribeOn(Schedulers.io()) .scanWith(initialState, reducer ) .onErrorReturn { ViewState(error = it) } .distinctUntilChanged() }
  11. Reducing Intent to State private val reducer = BiFunction<ViewState, Intent,

    ViewState> { previousState, intent -> when (intent) { is Data -> previousState.copy( isLoading = false, itemName = intent.itemName, itemPrice = intent.itemPrice, dropdownValues = listOfNumbers(intent.maxNumberInDropdown) ) … else -> previousState } }
  12. Caveats 1. High Learning Curve; 2. Boilerplate code to setup;

    3. Special handling of alert states, animations, etc;
  13. Caveats 1. High Learning Curve; 2. Boilerplate code to setup;

    3. Special handling of alert states, animations, etc; 4. Redundant UI rerendering.
  14. 1. Single Source of Truth; 2. Unidirectional Data Flow; 3.

    Immutable objects; 4. Clear sequence of changes. Debugging
  15. Testing @Test fun `should return a viewState with error when

    button is clicked`() { //given val viewState = MVIView.ViewState() val expectedError = Throwable("Something Bad Happened") val expectedStates = arrayOf( viewState, viewState.copy( error = expectedError, isLoading = false ) ) //when ….
  16. Testing @Test fun `should return a viewState with error when

    button is clicked`() { //given …. //when val resultsObserver = viewmodel.bindIntents(intentSubject).test() intentSubject.onNext(MVIView.Intent.ButtonClicked) //then resultsObserver.assertValues(*expectedStates) }
  17. 1. Consistency and predictability; 2. Easy debugging, testing, reproducing of

    crashes; 3. Focus on business needs. MVI gives you:
  18. Resources 1. https:/ /hannesdorfmann.com/androi d/mosby3-mvi-1/ 2. https:/ /abhiappmobiledeveloper.med ium.com/android-mvi-reactive-archit ecture-pattern-74e5f1300a87

    3. https:/ /www.raywenderlich.com/817 602-mvi-architecture-for-android-tuto rial-getting-started 4. https:/ /www.youtube.com/watch?v= UsuzhTlccRk&ab_channel=SquareEng ineering 5. https:/ /downloads.ctfassets.net/2gru fn031spf/6lC85sBYmQmSCoiqYiQOM o/a7ccea6ded95c6a1c5d3f46d74773 da1/Sergey_Ryabov_Kak_prigotovit_k horosho_prozharennyy_MVI_pod_And roid.pdf 6. https:/ /www.godaddy.com/engineeri ng/2021/11/05/android-state-manag ement-mvi/