I'm trying to understand why is ActivityRetainedScoped introduced for DI in Hilt. It looks to me that the scope is identical to what ViewModelScoped should do. I was under the impression that scoped worked like this:
AppScope (singleton) > ViewModelScope > ActivityScope > ViewScope > ...
But this graphic kinda hints that ViewModel and Activity scopes are... siblings?
According to the docs:
"ActivityRetainedComponent lives across configuration changes, so it
is created at the first Activity#onCreate() and destroyed at the last
Activity#onDestroy()."
Well, so does the view model, no?
I'm pretty sure view models survive config changes (that's the whole point if having them in the first place)
What is ActivityRetainedScoped? How is it different from VM scope? Why does google likes over complicating things that should be conceptually simple
https://developer.android.com/training/dependency-injection/hilt-android
Well, even tho ActivityRetainedScope and ViewModelScope are Siblings and one could think that makes them the same, there are in fact not.
Well, so does the view model, no? I'm pretty sure view models survive config changes (that's the whole point if having them in the first place)
Well yes, but actually no. A Viewmodel does survive configuration changes, but only of its scoped lifecyleowner. So let's think of the following scenario:
You have two dependencies, one is ActivtyRetainedScoped and the other one is viewmodelscoped.
When you now inject the viewmodeldependency inside the viewmodel and the lifecycleowner of the viewmodel is an activity, then you are right, both the ActivtyRetainedScope and the ViewmodelScope would not make any difference.
But now let's assume the lifecycleowner is a fragment, in this case, the viewmodelscoped dependency would "die" when you navigate out of the fragment and the activtyretainedscope dependency would outlive the viewmodelscoped one.
I hope I could explain the difference between them both. Kinda hard with those "scopes" etc. when English is not your native language. Also, I am not 100% if this is the correct answer
Related
Google states to use SharedViewModel in case of communication between Fragments by scoping it to the activity.
In a Single Activity Application, this means that the activity will be littered with ViewModels which might not be needed anymore and they will stay there for the whole lifecycle. Considering some example like a extended sign up flow or something considering a few screens.
One way recommended was using a parent Fragment as scope, but that is only possible if there is a parent fragment. It can be just different Fragments.
I came up with the following solution, I want to know how viable or how bad is it and is there any better way around?
Considering I have two Fragments called ExampleOneFragment and ExampleTwoFragment for sake of simplicity and I want them to have a shared scope without actually scoping it to the activity. Let's say I want to update text view in ExampleOneFragment from ExampleTwoFragment so I create a SharedViewModel like this for both
For ExampleOneFragment it will be:
private val mSharedViewModel by lazy {
ViewModelProvider(this).get(SharedViewModel::class.java)
}
And For ExampleTwoFragment I came up with this:
private val mSharedViewModel by lazy {
ViewModelProvider(supportFragmentManager().findFragmentByTag(ExampleOneFragment.TAG) ?: this).get(SharedViewModel::class.java)
}
This seems to be working, but I don't know what kind of issues can this cause.
Some Other Solutions are which I found:
According to #mikhehc here We could actually create our very own ViewModelStore instead. This will allow us granular control to what scope the ViewModel have to exist. But I don't understand how to make it work for Fragments?
Secondly, is the hacky way of scoping it to the activity still, but clearing it out via dummy viewmodel by using same key which I found here
Could anyone guide me what is the right approach? I cannot shift to NavGraphs since it is an already up and running project and scoping to activity just feels wrong. Thanks.
This seems to be working, but I don't know what kind of issues can this cause.
This code will only work if:
An ExampleOneFragment is created first, always
It is added to the same FragmentManager that ExampleTwoFragment uses, via a tag of ExampleOneFragment.TAG, always
If either of those assumptions fail, you will wind up with separate viewmodel instances, because supportFragmentManager().findFragmentByTag(ExampleOneFragment.TAG) ?: this will resolve to this.
But I don't understand how to make it work for Fragments?
You would use it the way it is shown in that answer, or by anything else that accepts a ViewModelStoreOwner. In this line:
val viewModel = ViewModelProvider(myApp, viewModelFactory).get(CustomViewModel::class.java)
You would get myApp from your Fragment as:
val myApp = requireContext().application as MyApp
Personally, I think the solution that you pointed to is very risky. The ViewModelStore lives for the entire lifetime of the process and is never cleared. You will wind up sharing your viewmodel across everything, and everything done by that viewmodel is leaked. The concept of creating a custom ViewModelStoreOwner is fine, but you should be doing something to tie the scope of that owner to the relevant lifetime for the viewmodels that it stores. The answer tries to dance around that in its last paragraph; too many developers will ignore this and run into problems.
One way recommended was using a parent Fragment as scope, but that is only possible if there is a parent fragment. It can be just different Fragments.
Your app is not written in a way to make automatic ViewModelStoreOwner management available "out of the box", then.
In the end, you are looking for a ViewModelStoreOwner to be shared between these two fragments. In your first solution, you are trying to hack that by using the ViewModelStoreOwner from one of those fragments, which only works if you can reliably choose which fragment that is. In the solution you pointed to in that other answer, you are trying to hack that by intentionally leaking a ViewModelStoreOwner.
There may be other approaches that you could consider, depending on your circumstances. For example, there may be some option with your dependency inversion framework (Dagger/Hilt, Koin, etc.) to rig up a ViewModelStoreOwner that is tied to a specific pair of fragment instances.
For what other purposes we can use ViewModel for?
I have been asked if an Activity is locked to portrait mode, then is this relevant to use ViewModel?
If Yes why?
In general, ViewModel is an architectural component that "couples" a view with a model. From the architectural point of view it is relevant.
Additionally to the general component, Android Jetpack's ViewModel is also lifecycle-aware. You can treat it as a conveniency bonus.
With that being said, if the app is not supposed to have configuration changes (screen rotation is one of those) you may use any CustomViewModel.
Let's see a brief explanation about these terms in a nutshell.
Android-Activity is independent, it can present the view itself.
Android-Fragment can't run independently and requires an Android-activity as its host, which means that when the Android-activity is/was destroyed then the Android-fragment will be too. The Android-fragment does not strictly specify which Android-activity should or can host it, as long as it is an Android-activity then you're good.
Both Android-activity and Android-fragment can have ViewModel.
ViewModel is always dependent on Android-activity or Android-fragment lifecycle, which means that when the Android-activity/ Android-fragment is/was destroyed then the ViewModel will be destroyed as well (except for configuration changes).
Let's see some relationships defined in the UML class diagram.
Composition (not sure, since the Android-activity doesn't depend on the Android-fragment)
Aggregation (not sure, since the Android-fragment will be destroyed if the Android-activity destroyed the same with the ViewModel, where aggregation encourage that both parties can live with its own)
Association (maybe yes or no?)
Dependency (maybe yes or no?)
Generalization (definitely no)
Realization (definitely no)
Now, to model them on UML Class Diagram, there are some questions:
What is the relationship between the Android-activity and the Android-fragment?
What is the relationship between the Android-activity/ Android-fragment and ViewModel?
In both cases, the appropriate relationship is a composition, because the child is destroyed when the parent is destroyed.
Multiplicity 1 means that every Fragment requires an Activity.
Multiplicity 0..* means that multiple Fragments can be part of the same Activity, but an Activity does not necessarily have any Fragment.
The relationships for ViewModel are as follows:
UPDATE: In your question, you have added an exception for configuration changes. In that case, the relationships with ViewModel are not compositions. But if you don't want to let this exception influence your model, you could add a note in the diagram explaining the exception.
In the Using Dagger in your Android app codelab tutorial they use an activity scoped regular class that acts as a ViewModel like so
#ActivityScope
class RegistrationViewModel #Inject constructor(val userManager: UserManager) {
...
}
That makes ViewModel injection by Dagger very simple but won't we loose anyting if we don't derive from the architecture components ViewModel class?
In general, the code labs are related to some topic and they try to explain only this topic. Here it is Dagger, not Architecture Components. Yes, you can lose some functionality, but if they still can make their point - it does not matter.
Also if they make the app work with only plain java objects it means that they don't need the extra functionality from the ViewModel, they wrote less code so even better.
I also want to point out that the explanation that "you are losing ViewModel.onCleared" is "the smallest problem". What is the "main feature" of the VM is that you can share the same instance through the life cycle of the same Activity/Fragment or that you can share it between different Activity/Fragment.
And onCleared is something that should be used with caution because it means in some situations that you are trying to clear a reference to a thing you should not be holding in the first place.
what would it be the best practice about placing Presenters in a Scope?
Could we have Presenters on #Singleton or #AppScope without any problem?
Should they be placed in an #ActivityScope in order to destroy them each time the activity is destroyed?
what would it be the best practice about placing Presenters in a Scope?
Usually a presenter should be in some scope. Not placing it in any scope will lead to problems, as every time you request a presenter it would create a new one.
Which scope you choose mostly depends on your programming style, but the most common would probably be #PerActivity, as a scope that follows the lifecycle of the Activity. (the same way you can use something like #PerFragment with Fragments and their lifecycle)
Could we have Presenters on #Singleton or #AppScope without any problem?
Yes and no. Longer living objects referencing shorter lived ones (e.g. a #Singleton object that references an activity-lifecycled one) usually is not a good practice that might lead to memory leaks.
You can avoid these issues by properly adding / removing your shorter lived objects (e.g. add in onCreate, remove in onDestroy) or using WeakReference.
Some programmers will keep their presenters as #Singleton or in some similar fashion and swap views, but again this depends on how you prefer your code. It will work, but you must make sure what objects you reference and to clean up afterwards.
Should they be placed in an #ActivityScope in order to destroy them each time the activity is destroyed?
This is by far the easiest option, since you have no problem referencing the Activity or anything else that depends on it. You most likely won't have to worry about memory leaks or other issues this way.
In the end its your code and you have to do what works best for you.