Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes occurring independent of user interaction #7

Open
ScottCooper92 opened this issue Feb 15, 2019 · 11 comments
Open

Changes occurring independent of user interaction #7

ScottCooper92 opened this issue Feb 15, 2019 · 11 comments
Labels
question Further information is requested

Comments

@ScottCooper92
Copy link

Hey there, I've been reading the wiki to figure out if this library is right for me. I like the idea of mapping actions into changes and having the UI respond to state changes but I feel like the use case might be a bit narrow.

Quite often a change occurs as a result of something other than user interaction, as an example let's say my app is polling a server for live sports results, normally these changes would come through the backend of the app and update a property in the ViewModel/Presenter which the UI is observing.

Is it possible (or even necessary) to represent external events as actions and treat them the same as user interaction, especially if they aren't coming from the UI layer?

@jshvarts
Copy link
Contributor

Hey @glurt. You say that the change occurs as a result of something other than user interaction yet the UI is observing the result of that change? Then why not have an Action from UI initiating the lookup and emitting the States as Flowable or Observable streams so whenever polling a server results in new data, new State will be emitted to the UI.

@Ethan1983
Copy link

@jshvarts Even though it works, that approach leads to some tightly coupled design. UI needs to be aware of the source which can influence the data like network etc.

A better design is for UI to just rely on the redux store as source of truth and not care about how the actions/events causes data change. Other user initiated UI actions, platform initiated events (broadcast, sensor, location update etc), backend server triggered events (domain logic, push notification etc) would just need to update the store. All consumers of the store (analytics, logging) including UI will just work out of the box.

@jshvarts
Copy link
Contributor

Can you point me to some other redux implementations on Android that do something similar? We have not had the need to do this but PRs are welcome

@Ethan1983
Copy link

Ethan1983 commented Feb 16, 2019

Not a redux implementation per se but Room's LiveData update is an example. It doesn't matter how the data was updated in the database (network/UI or other background tasks), all registered observers (including UI if in resumed state) are notified of the new update via LiveData. It offers a better separation of concerns, makes testing easier and encourages a plug-in design.

As it looks many implementations of UDF works well when UI is the only beneficiary/driver of the data store but not with consumers like services, work manager tasks etc.

@ScottCooper92
Copy link
Author

I've just finished refactoring one of the screens in my app which is using Room as a datasource, the ViewModel is setup to receive the initial Action from a Fragment to load some data from the Repository, my Repo gives me back a Data Class containing multiple Observables, one of them is Observable<PagedList>, the others relate to the network state and errors encountered when requesting more data to load into Room.

Each of these Observables is mapped to a Change and they're all then merged into a single stream to pass through the reducer to then be mapped into a State. Any time one of them fires, the State changes and the UI is updated.

So in essence, we can treat external changes as Actions without explicitly defining them in the ViewModel, the ViewModel just needs to know how to map these external events to a Change.

I'm not sure if this is the best way of approaching this but it seems to work for my scenario, I'd imagine that if you needed to listen for broadcasts or notifications you could map them into Changes like I am.

@jshvarts jshvarts added the enhancement New feature or request label Mar 17, 2019
@jshvarts jshvarts added question Further information is requested and removed enhancement New feature or request labels Mar 30, 2019
@jshvarts
Copy link
Contributor

So looks like we have a good solution here. Treat background/external changes as Actions just like we treat user-initiated events as Actions. After all the Action is just a simple data container. Whether an Action is user-initiated or an external, it will go thru generating Change(s) and State(s). If anyone has a different suggestion, please share.

@curioustechizen
Copy link

^ This seems to be the most logical approach. It lends itself to easy testing, and it keeps everything consistent.

My only concern with this approach is that it could end up exposing some Actions to the Activity that should not be.

//Inside MyActivity

dispatchAction(Action.UserInitiatedAction) //OK

dispatchAction(Action.ExternalAction) //Not all ExternalActions should be visible to Activity

Depending on how you declare your Actions, perhaps some visibility modifier magic might mitigate this.

@ScottCooper92
Copy link
Author

It may not even be necessary to define the Actions for external changes.

For UI it makes sense because the Activity/Fragment knows about the ViewModel and needs to send something to the ViewModel so that it knows what it needs to react to. UserInitiatedAction is a good way of representing that.

External events are probably going to come from multiple places that don't know about the ViewModel, like repositories, callbacks etc. therefore they don't know about the Actions defined within. If the ViewModel implements a listener it can simply emit a Change using a Subject and have that Subject feed into the the reducer like so

private val externalChange = PublishSubject.create<Change>()
....
init{
    .....
    disposables.add(
        Observables.merge(userChange1, userChange2, externalChange)
        ....
    )
}

override fun externalChangeListener(data: Any){
    externalChange.onNext(Change.External(data))
}

@jshvarts
Copy link
Contributor

jshvarts commented Apr 1, 2019

Interesting idea @glurt. Will try it out

@JotraN
Copy link
Contributor

JotraN commented Apr 2, 2019

@glurt, that's something similar to what we've done - except rather than an explicit Change in the view model, we just bind it to the action's stream.

fun bindActions() {
     val load = actions.ofType<Action.Load>()
            .switchMap { 
                useCase.load() // Our normal load stuff.
                    .map<Change> { Change.Load(it) }
                    .onErrorReturn { Change.LoadError(it) }
                    .mergeWith(useCase.triggerSomePublisherEmitterThing().map<Change> { Change.External })  // Our external change stuff.
                    .onErrorReturn { Change.ErrorWithPublisherEmitterThing(it) }
                    ...
            }
}

@curioustechizen
Copy link

@glurt This is how we have implemented it right now: ViewModel adds a listener for the external change, and in response to the change it emits a Change object. The problem is testing this is a little more involved than testing it if it were to dispatch an Action.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants