-
Notifications
You must be signed in to change notification settings - Fork 500
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
[OpenQuestion/Idea] One-shot event handling #426
Comments
Thanks for the writeup and sharing your approach. I personally want to avoid annotation processors as much as possible, as they add quite a bit of complexity and also increase compilation time. Many things can also be done by leveraging kotlin language techniques instead. This is coming from my experience maintaining several annotation processor based libraries, such as Epoxy. What do you think about using reflection instead of annotation processing? You could have a function like this in your base viewmodel fun <T> reset(property: KProperty<S, Async<T>>) Which could then use reflection to do the same resetting behavior. Usage would be like is Success -> {
viewModel.reset(HelloWorldState::moveToNextScreen) |
@ema987 @elihart Exposing something like that would basically give anybody access to clear a specific state property which is something that should be owned by the ViewModel. A reset function can be a single line like:
I don't think it's bad to enforce being explicit about what can and can't be cleared this way |
@elihart @gpeal thank you for your responses! @elihart the use of reflection was another approach I tried to follow (after discussing with my friend @reavcn) which I didn't mention before. On the base viewModel you have two functions which you can use to reset the state's properties:
@gpeal I agree with you. To try to enforce this, I've made an annotation I think it can probably be improved (the functions could be made as extension on BaseMvRxViewModel, I presume) but the sample currently works and you can try it at https://github.com/ema987/MvRx/tree/feature/state-resetter-reflection Thank you both for making me think about this. |
@ema987 I think your sample actually adds more code and overhead than just making a reset function on your view model since you now need a static const default value and annotation. I would still recommend this in one of two ways:
My main issue with the reset function is that the ViewModel no longer exposes an explicit API of actions. There is this entirely new implicit API of resettable things that the ViewModel has no control over and can easily lead to unexpected states or unintended side effects. A second issue with this is that sometimes, it only makes sense to set or clear multiple properties at the same time and this has no way of enforcing that whereas it could be encapsulated and testable in either of my examples above. |
@gpeal the static const default value is actually needed if you want to have a default value different than I see your point when you say The approach you suggest, if I'm understanding it correctly, is to manually write in the state or in the viewModel the reset() function for each property which needs a reset (this is actually what you would have with the annotation processing approach). Even if this is 100% explicit and this is what I was doing at the beginning, in the long run it becomes annoying to write, that's why I was looking for a better approach which could sacrifice some explicitness in favour of less code to write. Unfortunately all of the approaches have their flaws. |
@ema987 I'm surprised that the rest functionality is so common for you. Maybe there is another approach. I've definitely seen the approach used but maybe just once or twice each in <1% of all MvRx screens I've seen. |
In my opinion this is a rare enough case, with a minimal boilerplate savings, that optimizing it with the reflection or annotation processor approach in the core library is not worthwhile. As a library I think it is best to lean towards explicitness and simplicity at a slight cost to verbosity. If developers like you want to build on top of this and have a simpler pattern just for your own usage then I recommend putting the reflection approach in your base viewmodel so you can use it in your project, and customize it for however you want, but I agree with @gpeal that it compromises data integrity so is best not to mainstream in mvrx |
Hello,
first of all thank you for this amazing library!
This issue is about an idea to handle one-shot events like the one explained in #400 or similar issues.
I also encountered similar scenarios, and after trying using
selectSubscribe()
anduniqueOnly()
I wasn't fully satisfied with the approach.It moves some UI logic away from the
invalidate()
function where I know the UI logic is.I eventually started resetting state's asyncs as soon as the UI consumed them, like the following example:
State:
Fragment:
ViewModel:
I started using this approach most of the time and I liked it, but everytime I needed to write these reset functions.
The next approach I followed was writing a general function like this:
So, to use the same example as before, instead of calling
viewModel.onMoveToNextScreen()
you would callviewModel.reset(HelloWorldState::onMoveToNextScreen)
.This works, but the code I was writing started to became boilerplate, so I though about annotation processing.
This is what I came up with:
3 annotations which should be used on ViewModel, State and its properties.
ResettableViewModel
ResettableState
ResettableProperty
For every property annotated with
ResettableProperty
inside a state annotated withResettableState
, the annotation processor will create an extension function on the corresponding viewModel annotated withResettableViewModel
which can be used to reset the property to its default value specified in the state.Here an example with the most relevant parts:
State:
Fragment:
ViewModel:
The functions
resetTitle()
,resetDescription()
andreset()
are generated by the annotation processor and can be called on the viewModel.The
reset()
function will reset all theResettableProperty
at once.For the assignment of the default values I followed the approach you use in Epoxy and Paris.
For the sake of simplicity I created some buttons with their click listeners to easily invoke them.
This is the generated code for
resetDescription()
:What I like of this approach:
What I don't like:
resetNAMEOFTHEPROPERTY()
name is probably not correct, I would like to change to something likeonNAMEOFTHEPROPERTYHandled()
, otherwise it seems like is the fragment to tell the viewModel what to do instead of the viewModel to react on something that happens in the fragmentsetState()
and I feel kind of bad because it seems you are callingsetState()
outside of the viewModel.You can see a working example at https://github.com/ema987/MvRx/tree/feature/state-resetter
Before opening a PR and improving the code I'd like to know from you if you like the idea and would like to have it in your repository or if you dislike it at all :)
Sorry for the long post, I tried to show you what was the full path that led me to this and I hope it makes it more understandable.
Please let me know. Thank you!
The text was updated successfully, but these errors were encountered: