[MVI] First draft of a reusable architecture#57
Conversation
Co-Authored-By: Joe Timmins <joetimmins@users.noreply.github.com>
| override fun render(state: SearchState) { | ||
| when (state) { | ||
| is Content -> { | ||
| if (searchInput.currentQuery != state.queryString) { |
There was a problem hiding this comment.
why do you do this check on the view?
There was a problem hiding this comment.
Good question:
TL;DR
Unless we are resuming from an activity destroyed, the textInput would be asked to render the exact same text that is already rendering every-time the user is typing something.
In this case I found that updating the textField too often would result in flashes in the UI and some missing character while typing too fast.
Context
The text in the input field is part of the state, in this way we can example restore it after an Activity has been destroyed and recreated.
That means that for every character introduced in the textInput:
- An Action is emitted ChangeQuery
- The middleware emit the relative Change SearchQueryUpdate
- The Store emits a new State
- The view is asked to render the new generated state
In complex UI we want to avoid to render changes that are not necessary or that can compromise the User Experience.
IDEALLY we should use some UI-Framework that implement the diffing of the changes to have the best of this architecture, as for example
Litho from Facebook
Domic from Lyft
As a simple workaround we can also simply debounce the ChangeQuery emissions so that we don't flood the components until the user has finished typing
There was a problem hiding this comment.
Yeah this doesn't sound like something the view should control, I would vote for debouncing the emissions 👍 this would allow you to change behaviour in the correct place even when the view is changed to a different one. Right now without me asking this question it feels your above explanation is hidden in the view, rather than shown more where control should happen (the intent?/ChangeQuery)
…n specific middleware to the reducer
033fbe0 to
ca4136a
Compare
[MVI] Reduce state with state changes
* mvi_reducer: Remove not needed null check Change changes to be a PublishSubject Use scan operator to reduce changes to a state Change wording to highlight that the query is updated add hideLoading and removeResults actions Expect SearchMiddleware to hide progress Move domain agnostic screen state and screen state changes from domain specific middleware to the reducer Rename domain specific changes to domain agnostic screen state changes # Conflicts: # ModelViewIntentSample/search/src/main/java/com/novoda/movies/mvi/search/domain/SearchDependencyProvider.kt # ModelViewIntentSample/search/src/main/java/com/novoda/movies/mvi/search/domain/SearchMiddleware.kt # ModelViewIntentSample/search/src/main/java/com/novoda/movies/mvi/search/presentation/SearchActivity.kt # ModelViewIntentSample/search/src/test/java/com/novoda/movies/mvi/search/domain/SearchMiddlewareTest.kt
MVI ViewModel
Description
This replaces a mvi'ish implementation of a movie search with a reusable implementation of MVI.
Considerations
First things first, this is the first iteration. There is lot's of room for improvements in our implementation of the pattern and for covering more use cases like screen orientation, one time events like (navigation, dialogs, etc.).
Furthermore, this PR introduces documentation around our implementation of MVI and the different components.
In the previous implementation, the
SearchResultsViewableandSearchInputViewabledid expose callbacks for user actions and methods to render a view state.The
SearchResultsPresenterdid register for these callbacks and forwarded the user actions to theSearchResultsModel. Furthermore, it observed the state from that model, mapped it to a view-state and forwarded this to the above-mentioned views.The
SearchResultsModelwas responsible to implement the business rules and to expose a stream of state changes according to them.We replaced the views with a single
MVIView<SearchAction, SearchState>which exposes anObservable<Action>and renders aSearchState.Furthermore, we moved all the business logic into the
SearchMiddleware<SearchAction, SearchState, SearchChanges>, which exposes anObservable<SearchChanges>.These
SearchChangesare then observed by theSearchReducer, which maps them to aSearchState.And finally, we introduced the
BaseStore<Action, State, Change>as a use case agnostic class, which collaborates with the above components to connect the different data streams.Data Flow
Paired with
@gbasile @zegnus @lgvalle @Michal-Novoda