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

Dealing with one-time effects #12

Open
jshvarts opened this issue Mar 30, 2019 · 7 comments
Open

Dealing with one-time effects #12

jshvarts opened this issue Mar 30, 2019 · 7 comments

Comments

@jshvarts
Copy link
Contributor

jshvarts commented Mar 30, 2019

Here is a scenario:

Your screen supports rotation. A particular Action can generate an error which should display a Snackbar.

How do we deal with it? Emit the error State initially but not after rotation to avoid displaying the Snackbar twice? What should the State be after rotation then?

Should we introduce a concept of Effects (single event type States)?

In the spirit of Roxie's lightweight way of doing things, it would be ideal to solve this with minimal code needed by the consumer apps.

@JotraN
Copy link
Contributor

JotraN commented Mar 30, 2019

I ran into a similar issue before with regards to States that navigate to another screen (IIRC the issue was we'd keep navigating to Screen B when we back out of it to Screen A because the same navigation State was being rendered each time). I just went with an intermediate action/state in this case.

I think the actual fix is using something like https://github.com/googlesamples/android-architecture/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java#L38 - since the state won't be emitted automatically, if this is what you meant by Effects - I'm down for implementing something like this.
I'd say ideally we just make our observable state a SingleLiveEvent rather than a MutableLiveData (this is assuming there isn't a use case where we'd need to render the state without explicitly calling setValue/postValue 🤔).

@jshvarts
Copy link
Contributor Author

So there would be a LiveData for regular State and SingleLiveEvent for effects? And the observing views would subscribe to both of them? And each effect would define what State to leave the state machine in prior to emitting the effect as SingleLiveEvent?

@JotraN
Copy link
Contributor

JotraN commented Apr 1, 2019

Jmmm, I was originally thinking of just using SingleLiveEvent for our State and not using (an explicit) LiveData at all - cause I couldn't think of a case where we'd want to render a State without an explicit setValue being called.

Thinking about it some more this probably wouldn't work, since we may have some loaded state that we always want rendered without an explicit action to load it.
Something like:

  1. Start fragment and enter loaded state via a load action.
  2. Rotate device to trigger configuration change stuff or w/e.
  3. Just render existing loaded state rather than trigger an explicit action to load again.
    So we'll probably have to go with two different States or a State and an Effect?

The super simple solution would just have our Rx chains that we want to act as SingleLiveEvent always emit another intermediate State after it's real state.

     useCase.loadStuff()
                    .toObservable()
                    .map<Change> { Change.Load(it) }
                    .onErrorReturn { error -> Change.Error(error) }
                    // Intermediate state here
                    .flatMap { Observable.just<Change>(Change.Idle) }
                    .subscribeOn(Schedulers.io())
                    .startWith(Change.Loading)

When onActivityCreated or whatever gets called from configuration change or fragment replacement (I've run into this issue with the back button and the android navigation component because it doesn't support add for fragment transactions 😠) and we observe the VM's state we just render Idle rather than the last state (which could be Error or Loaded, would just need to move some code around to have it only apply to the Error change).

@ScottCooper92
Copy link

I've just run into the same problem when trying to use the Navigation Component. I was thinking along the same lines as you by introducing another emitter using SingleLiveEvent.

That way you have States which change the state of the UI and you have Events/Effects that trigger something like navigation, snackbars, dialogs, permission requests etc

The question is how to split Changes into either States or Events, an example would be the user pressing a "locate me" button, the Action triggers a Change that updates the State to show a progress spinner, whilst the Event needs to trigger a permission request. It gets trickier when we need to do something based on the results of the Event, did they accept it or did they reject it etc

@Ethan1983
Copy link

@glurt Just FYI. Trying to relay navigation update as event/effect (SingleLiveEvent) fails in an edge case with lifecycle and fragment transactions.

kaushikgopal/movies-usf-android#19

@jshvarts
Copy link
Contributor Author

jshvarts commented Aug 2, 2019

This seems like a good way to handle single events https://ryanharter.com/blog/handling-transient-events/ events can be dispatched from within bindActions()

@Ethan1983
Copy link

This seems like a good way to handle single events https://ryanharter.com/blog/handling-transient-events/ events can be dispatched from within bindActions()

Checked it, the subscription until onDestroy still has the same problem while the app is backgrounded and the event notification (from PublishRelay) tries to perform a fragment transaction on a paused app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants