Comments (17)
@gpeal I'm not sure I see how state restoration changes the interface (or mental model). Args should be the same in the state restoration case vs. the normal case.
@Mishkun I do see that I misread the suggestion a bit. I don't think we should make the ViewModelFactory responsible for creating initialState
. I was more imagining just one extra arg from before:
class MyViewModel(private val args: MyArgs, initialState: MyState) : BaseMvRxViewModel(initialState) {
init {
// just use the args
args.let { // ... }
}
companion object : MvRxViewModelArgFactory<MyState, MyViewModel> {
@JvmStatic
override fun create(activity: FragmentActivity, initialState: MyState, args: MyArgs): MyViewModel {
return MyViewModel(args, intialState)
}
}
}
from mavericks.
Thanks for the suggestion. This seems reasonable to me. We initially believed that giving access to the activity (and in turn DI frameworks such as Dagger) would largely cover this, but I actually had an engineer at Airbnb have a similar need yesterday. Any concerns @gpeal?
from mavericks.
@Mishkun @BenSchwab The tricky part that led us to the current world is that MvRx handles process restoration so your ViewModel may be instantiated with non-initial state (restored state).
We wanted the mental model to be args -> initial state -> subsequent state
In the state restoration case, you would be given initial args + subsequent state which creates a potentially confusing world. I see the advantage of passing in args here and I'm not opposed to it, we just need to make sure that it makes sense for the view model restoration case as well.
from mavericks.
@gpeal good note about state restoration, haven't think about that. To handle that case, we can add to our factory interface a restore
method, that would give us the args and restored state so we can initialize viewmodel properly.
To reduce the boilerplate in simple cases we can also provide a default implementation that will call create
and then set the restored state.
from mavericks.
Sometimes args
is not enough to construct the state. My preference is to create the initial state inside view model. This decouples state and args, and will give us enough flexibility.
class MyViewModel(
private val x: X,
private val y: Y,
private val foo: Foo
): BaseMvRxViewModel<MyState>() {
override val initialState = MyState(
x = x,
y = y,
foo = foo
)
companion object : MvRxVMFactory {
override fun create(args: MyArgs) {
val someDaggerComponent = ...
return MyViewModel(args.x, args.y, someDaggerComponent.foo)
}
}
}
Or this
class MyViewModel(
private val args: MyArgs,
private val foo: Foo
): BaseMvRxViewModel<MyState>() {
override val initialState = MyState(
x = args.x,
y = args.y,
foo = foo
)
companion object : MvRxVMFactory {
override fun create(args: MyArgs) {
val someDaggerComponent = ...
return MyViewModel(args, someDaggerComponent.foo)
}
}
}
Another example is with generic state:
data class MyState<T>(
val x: Foo,
val t: T
)
class MySuperViewModel<T>(
val x: Foo,
val initialT: T
) {
override val initialState = MyState<T> {
x = x,
t = initialT
}
}
class MySubViewModel(
val x: Foo
): MySuperViewModel<String>(x = x, initialT = "Hello") {}
from mavericks.
@hellohuanlin You can instantiate state in the factory method of a ViewModel
. This would be much more cleaner approach involving real DI instead of fake one.
Real DI constructs everything needed to object A completely outside this object.
Fake DI gives A some deps that A glues together
Also you loose benefits of state management done by MvRx
With suggested approach in initial proposal you can easily move initial state creation to the ViewModelFactory and still be compliant with all the framework features
from mavericks.
@Mishkun yes moving it into ViewModel
feels like a better approach.
from mavericks.
I got the same question when I want to reuse the same ViewModel with a different configuration in the same activity.
from mavericks.
@bearprada you can use keyFactory
for that:
by fragmentViewModel(keyFactory = { "foo" })
Every key will create a separate ViewModel.
from mavericks.
What if we have two lists on the same page(Fragment). One list shows category A's items, another list shows category B's items. We want to re-use the same ViewModel's implementation.
Right now, here is no way to inject two different Args objects in a Fragment, no?
Sorry, my question might not suitable for this thread, and it might not your original design.
from mavericks.
@bearprada You can make your fragment args contain as much information as you want because it's just a parcelable class. We recommend using a kotlin data class with @Parcelize
. You can store the information you need for both view models there.
from mavericks.
@Mishkun @bearprada @hellohuanlin @BenSchwab Is #148 sufficient to resolve this?
from mavericks.
@gpeal I don't think so. I think the rationale here is that if your state needs to reference a property that is stored in something other than args (state in a dagger component for example), you can't put in it your state without making it artificially nullable. Even with fragmentFactory, the state will be created before the dagger component is easily accessible.
My thought of a pretty easy fix is to introduce an optional fun initialState(activity/fragment): S
inside ViewModel Factories. Internally, we would take the result of this function, and then apply state restoration, before passing to createViewModel
.
from mavericks.
@BenSchwab That could work actually!
from mavericks.
Played around a bunch, and couldn't find a clean fully backwards compatible solution. This is what I am leaning towards now:
interface MvRxVMFactory<S: MvRxState> {
fun create(viewModelContext: ViewModelContext): BaseMvRxViewModel<S>? = null
fun initialState(viewModelContext: ViewModelContext): S? = null
}
sealed class ViewModelContext {
abstract val args: Any?
}
class ActivityViewModelContext(val activity: Activity, override val args: Any?) : ViewModelContext()
class FragmentViewModelContext(val activity: Activity, val fragment: Fragment, override val args: Any?) : ViewModelContext()
This approach has a few advantages:
- One factory for both activity scoped and fragment scoped VMs. Currently, if you use a
FragmentViewModelFactory
but scope it to an activity, you will crash. The use of a sealed class makes acknowledging this possibility explicit. Args
are weakly typed in this model (which I think is fine, because inherently they are loosely typed). This keeps the model consistent with the constructor based state creation, which could have many constructors with different arg types.- Decouples args from state -- you can access args just for use in ViewModel
- Allows DI of state objects by giving access to activity/fragment.
- Nullable return types for state creation and view model creation allow clean fallbacks to default factories.
from mavericks.
@BenSchwab that looks pretty good to me - the benefits you listed all seem great.
One thing - are you sure weakly typed args are a good idea? I think in most cases there will only be one type, and if somebody has a case for multiple args types they can use Any
explicitly. I'm concerned that not providing strong typing would lead to more boilerplate, and making it harder to catch the error case where the wrong args type is passed.
from mavericks.
Good point @elihart. I like the idea of preferring typed args, but allowing Any
as an escape. Only downside I see is it forces one more generic into the factory interface.
from mavericks.
Related Issues (20)
- Type com.airbnb.mvrx.AndroidStrictModeExtensionsKt is defined multiple times HOT 1
- possible incompatibility with Kotlin coroutines 1.7.0 HOT 2
- Using androidx.lifecycle:lifecycle-runtime-ktx:2.6.1 problems HOT 13
- Fail State call twice and make error HOT 2
- how to use stateflow in MavericksViewModel HOT 2
- How can I use MavericksView in a View or a Layout
- compare with mvc,mvp,mvvm HOT 2
- The viewModel() method may be missing the postInvalidate() logic. HOT 2
- Hilt and ViewModel initialization with Navigation Component arguments HOT 6
- Exploration of the correct way to update state with List<Data> in state HOT 1
- Ensure that your state properties properly implement hashCode. HOT 7
- I wanted to use the latest version of Epoxy, Mavericks, Paging3 (Epoxy Paging3), but couldn't find the Demo. Is it possible to combine all three?
- java.lang.SecurityException: One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED should be specified
- java.lang.IllegalStateException HOT 1
- Value classes break State class no zero argument constructor usage HOT 1
- Discuss whenStarted function deprecation HOT 7
- I am using MavericksViewModel on Compose, I want to get the LifecycleOwner object in ViewModel how do I need to get this? HOT 3
- Crash when creating MavericksViewModel HOT 6
- Combining Mavericks with "custom" network response and error handling HOT 1
- Question - collectAsStateWithLifecycle HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from mavericks.