I want to have more than one same Composable as a childs of a parent Composable. Each one of them have to request different data to show it to the user.
Is there any way to create different instance of ViewModel to each Composable? By default the ViewModel it's associated to the single activity of the project I think, but I don't know if I could have different ViewModel instances or if I've to share ViewModel and manage the state of each composable inside the ViewModel.
I cannot create different ViewModel because I don't know how many Composables of this type we'll have (I receive all of the dynamically).
Currently I've using Hilt
#HiltViewModel
class SampleViewModel #Inject constructor(
private val sampleUseCase: sampleUseCase,
): ViewModel() {
....
}
I've been looking for a solution but I haven't found none.
Related
When I want to share a view model between various views I would use by viewmodels. I recently began looking into hilt and was wondering if hiltviewmodel would accomplish the same thing? That is to allow me to share a single(same instance) of a viewmodel?
by viewModels():
property delegate.
the first time create, next time return the same instance(in the same scope, the scope is the same activity)
= hiltViewModel():
only used in #Composable annotated function.
the first time create, next time return the same instance(in the same scope, the scope is NavGraph, if no graph, fragment/activity)
you 'd best build a demo and practise it, and log the instance to see if the same instance.
val viewModel: ViewModelA by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HiltDemoTheme {
val viewModel2: ViewModelA = hiltViewModel()
val viewModel3: ViewModelA = viewModel()
val viewModel4: ViewModelA by viewModels()
Log.d("Jeck", "$viewModel")
Log.d("Jeck", "$viewModel2")
Log.d("Jeck", "$viewModel3")
Log.d("Jeck", "$viewModel4")// the four get the same instance
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
}
}
}
Documentation:
Hilt and Navigation
When using Navigation Compose, always use the hiltViewModel composable
function to obtain an instance of your #HiltViewModel annotated
ViewModel. This works with fragments or activities that are annotated
with #AndroidEntryPoint.
The ViewModel annotated with #HiltViewModel will be available for creation by the HiltViewModelFactory and can be retrieved by default in an Activity or Fragment annotated with #AndroidEntryPoint by using ViewModelProvider or the by viewModels().
Also, by viewModels returns a property delegate to access ViewModel by default scoped to the fragment. Remember viewModels is not only used with Hilt, but it can be used to provide simple ViewModel instances as well when not using Hilt.
On the other hand, #HiltViewModel, tells Hilt that this ViewModel can be injected into other classes marked with #AndroidEntryPoint as well as allowing Hilt to inject other dependencies into this ViewModel.
All Hilt ViewModels are provided by the ViewModelComponent which follows the same lifecycle as a ViewModel, and as such, can survive configuration changes. To scope a dependency to a ViewModel use the #ViewModelScoped annotation.
A #ViewModelScoped type will make it so that a single instance of the scoped type is provided across all dependencies injected into the ViewModel. Other instances of a ViewModel that request the scoped instance will receive a different instance.
If a single instance needs to be shared across various ViewModels, then it should be scoped using either #ActivityRetainedScoped or #Singleton.
[Source: https://developer.android.com/training/dependency-injection/hilt-jetpack#viewmodelscoped]
And if the shared/single ViewModel is among different fragments, you could bind the lifecycle of the injected ViewModel to the parent Activity by by activityViewModels.
More information:
https://dagger.dev/api/latest/dagger/hilt/android/lifecycle/HiltViewModel.html &
https://dagger.dev/hilt/view-model.html &
https://developer.android.com/training/dependency-injection/hilt-jetpack
I am using Android compose and view model and I have a ViewModel that is scoped to a single Composable function which is a bottom sheet view, which is inflated using BottomSheetScaffold and I inject the ViewModel inside the Composable function using viewModel but I have a problem that viewModel function returns the same viewModel instance created before when I open the bottom sheet again.
In other words how to scope the ViewModel to a Dialog Composable function which is not related to NavHost and backStack
#Composable
fun ComposableExample(
) {
val viewModel: ExampleViewModel= viewModel() // createdOnlyOnce and always returns old instance
}
As per this issue, Compose does not offer any mechanism to scope ViewModels to an individual #Composable - any ViewModels you create outside of a NavHost's destination is scoped to the activity/fragment that contains your ComposeView/where you call setContent and, thus, lives for the entire lifetime of your Compose hierarchy - that's why you always get the same instance back.
Note that Navigation Compose has existing feature requests for supporting dialog destinations and for supporting BottomSheetScaffold, which would bring the same scoping of ViewModels and state to those types of destinations as well. You should star those issues to get updates and indicate your interest (which then helps prioritize that work).
This library scopes ViewModels or any other object to the lifecycle of a Composable using the call viewModelScoped (or rememberScoped)
https://github.com/sebaslogen/resaca
#Composable
fun ComposableExample(
) {
val viewModel = viewModelScoped { ExampleViewModel() } // created Only Once and always returns old instance
}
PS: It also supports an optional key, in case you want to have multiple ViewModels of the same type with different data, like viewModelScoped(myId) { ExampleViewModel(myId) }
After recently migrating from Dagger to Hilt I started observing very strange behavior with respect to ViewModels. Below is the code snippet:
#HiltAndroidApp
class AndroidApplication : Application() {}
#Singleton
class HomeViewModel #ViewModelInject constructor() :
ViewModel() {}
#AndroidEntryPoint
class HomeFragment : Fragment(R.layout.fragment_home) {
private val homeViewModel by viewModels<HomeViewModel>()
override fun onResume() {
super.onResume()
Timber.i("hashCode: ${homeViewModel.hashCode()}")
}
}
#AndroidEntryPoint
class SomeOtherFragment : Fragment(R.layout.fragment_home) {
private val homeViewModel by viewModels<HomeViewModel>()
override fun onResume() {
super.onResume()
Timber.i("hashCode: ${homeViewModel.hashCode()}")
}
}
The value of hashCode isn't consistent in all the fragments. I am unable to figure out what else am I missing for it to generate singleton instance of viewmodel within the activity.
I am using single activity design and have added all the required dependencies.
When you use by viewModels, you are creating a ViewModel scoped to that individual Fragment - this means each Fragment will have its own individual instance of that ViewModel class. If you want a single ViewModel instance scoped to the entire Activity, you'd want to use by activityViewModels
private val homeViewModel by activityViewModels<HomeViewModel>()
What Ian says is correct, by viewModels is the Fragment's extension function, and it will use the Fragment as the ViewModelStoreOwner.
If you need it to be scoped to the Activity, you can use by activityViewModels.
However, you typically don't want Activity-scoped ViewModels. They are effectively global in a single-Activity application.
To create an Activity-global non-stateful component, you can use the #ActivityRetainedScope in Hilt. These will be available to your ViewModels created in Activity or Fragment.
To create stateful retained components, you should rely on ~~#ViewModelInject, and #Assisted~~ #HiltViewModel and #Inject constructor to get a SavedStateHandle.
There is a high likelihood that at that point, instead of an Activity-scoped ViewModel, you really wanted a NavGraph-scoped ViewModel.
To get a SavedStateHandle into a NavGraph-scoped ViewModel inside a Fragment use val vm = androidx.hilt.navigation.fragment.hiltNavGraphViewModels(R.navigation.nav_graph_id).
If you are not using Hilt, then you can use = navGraphViewModels but you can get the SavedStateHandle using either the default ViewModelProvider.Factory, or the CreationExtras.
Here's an alternative solution to what ianhanniballake mentioned. It allows you to share a view model between fragments while not assigning it to the activity, therefore you avoid creating essentially a global view model in a single activity as EpicPandaForce stated. If you're using Navigation component, you can create a nested navigation graph of the fragments that you want to share a view model (follow this guide: Nested navigation graphs)
Within each fragment:
private val homeViewModel: HomeViewModel
by navGraphViewModels(R.id.nested_graph_id){defaultViewModelProviderFactory}
When you navigate out of the nested graph, the view model will be dropped. It will be recreated when you navigate back into the nested graph.
As mentioned by other posts here, using the by activityViewModels<yourClass>() will scope the VM to the entire Activity's lifecycle, making it effectively a global scope, to the entire app, if it's one activity architecture everyone uses and Google recommends.
Clean, minimal solution:
If you're using nav graph scoped viewmodels:
Replace this:
val vm: SomeViewModel by hiltNavGraphViewModels(R.id.nav_vm_id)
with below:
val vm by activityViewModels<SomeViewModel>()
This allows me to use this VM as a sharedviewmodel between the activity and those fragments.
Otherwise even the livedata observers do not work, as it creates new instances and lifecycles that are independent from each other.
in my app I have a MainActivity which requires access to a ViewModel. I am injecting the ViewModel using DaggerHilt and the #ViewModelInject annotation. Additionally, I have two Fragments within the Activity that require access to the same ViewModel in order to pass data to each other using observables.
The problem:
I have found that whenever one of my Fragments go through onDestroy() its ViewModel is killed. This leads me to think that the Activity and Fragments are not sharing the same ViewModel.
My question:
Does anyone know if we are supposed to use scope annotations for ViewModels in Dagger Hilt? I didn't see this stated in the Hilt docs or the android dev tutorials/guides. I had assumed that they were making ViewModels app level singletons, which would make sense.
If we do have to use scope annotations for ViewModels, does anyone know which level is appropriate?
This is my viewmodel code:
class MainActivityViewModel #ViewModelInject constructor(
private val repo: Repo,
private val rxSharedPrefsRepo: RxSharedPrefsRepo,
private val resourcesRepo: ResourcesRepo,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
As per the Scoping in Android and Hilt blog post, using #ViewModelInject means that the objects you pass into the ViewModel are scoped to the ViewModel.
The scope of the ViewModel, however, is based on how you get the ViewModel (what ViewModelStore the ViewModel is associated with) - not anything that Hilt controls. If you use by viewModels() in a Fragment, then the ViewModel is scoped to the Fragment. If you use by activityViewModels() or by navGraphViewModels(), then the ViewModel would be scoped to the activity or navigation graph, respectively.
As mentioned in the blog post, if you want an object that is scoped to the activity and survives configuration changes, you can use Hilt's #ActivityRetainedScoped on any object and inject that object into both fragments.
Whether you should use #ActivityRetainedScoped or a ViewModel where you control the scope separately from Hilt is covered in the blog post:
The advantage of scoping with Hilt is that scoped types are available in the Hilt component hierarchy whereas with ViewModel, you have to manually access the scoped types from the ViewModel.
The advantage of scoping with ViewModel is that you can have ViewModels for any LifecycleOwner objects in your application. For example, if you use the Jetpack Navigation library, you can have a ViewModel attached to your NavGraph.
Hilt provides a limited number of scopes. You might find that you don’t have a scope for your particular use case — for example, when using nested fragments. For that case, you can fall back to scoping using ViewModel.
After recently migrating from Dagger to Hilt I started observing very strange behavior with respect to ViewModels. Below is the code snippet:
#HiltAndroidApp
class AndroidApplication : Application() {}
#Singleton
class HomeViewModel #ViewModelInject constructor() :
ViewModel() {}
#AndroidEntryPoint
class HomeFragment : Fragment(R.layout.fragment_home) {
private val homeViewModel by viewModels<HomeViewModel>()
override fun onResume() {
super.onResume()
Timber.i("hashCode: ${homeViewModel.hashCode()}")
}
}
#AndroidEntryPoint
class SomeOtherFragment : Fragment(R.layout.fragment_home) {
private val homeViewModel by viewModels<HomeViewModel>()
override fun onResume() {
super.onResume()
Timber.i("hashCode: ${homeViewModel.hashCode()}")
}
}
The value of hashCode isn't consistent in all the fragments. I am unable to figure out what else am I missing for it to generate singleton instance of viewmodel within the activity.
I am using single activity design and have added all the required dependencies.
When you use by viewModels, you are creating a ViewModel scoped to that individual Fragment - this means each Fragment will have its own individual instance of that ViewModel class. If you want a single ViewModel instance scoped to the entire Activity, you'd want to use by activityViewModels
private val homeViewModel by activityViewModels<HomeViewModel>()
What Ian says is correct, by viewModels is the Fragment's extension function, and it will use the Fragment as the ViewModelStoreOwner.
If you need it to be scoped to the Activity, you can use by activityViewModels.
However, you typically don't want Activity-scoped ViewModels. They are effectively global in a single-Activity application.
To create an Activity-global non-stateful component, you can use the #ActivityRetainedScope in Hilt. These will be available to your ViewModels created in Activity or Fragment.
To create stateful retained components, you should rely on ~~#ViewModelInject, and #Assisted~~ #HiltViewModel and #Inject constructor to get a SavedStateHandle.
There is a high likelihood that at that point, instead of an Activity-scoped ViewModel, you really wanted a NavGraph-scoped ViewModel.
To get a SavedStateHandle into a NavGraph-scoped ViewModel inside a Fragment use val vm = androidx.hilt.navigation.fragment.hiltNavGraphViewModels(R.navigation.nav_graph_id).
If you are not using Hilt, then you can use = navGraphViewModels but you can get the SavedStateHandle using either the default ViewModelProvider.Factory, or the CreationExtras.
Here's an alternative solution to what ianhanniballake mentioned. It allows you to share a view model between fragments while not assigning it to the activity, therefore you avoid creating essentially a global view model in a single activity as EpicPandaForce stated. If you're using Navigation component, you can create a nested navigation graph of the fragments that you want to share a view model (follow this guide: Nested navigation graphs)
Within each fragment:
private val homeViewModel: HomeViewModel
by navGraphViewModels(R.id.nested_graph_id){defaultViewModelProviderFactory}
When you navigate out of the nested graph, the view model will be dropped. It will be recreated when you navigate back into the nested graph.
As mentioned by other posts here, using the by activityViewModels<yourClass>() will scope the VM to the entire Activity's lifecycle, making it effectively a global scope, to the entire app, if it's one activity architecture everyone uses and Google recommends.
Clean, minimal solution:
If you're using nav graph scoped viewmodels:
Replace this:
val vm: SomeViewModel by hiltNavGraphViewModels(R.id.nav_vm_id)
with below:
val vm by activityViewModels<SomeViewModel>()
This allows me to use this VM as a sharedviewmodel between the activity and those fragments.
Otherwise even the livedata observers do not work, as it creates new instances and lifecycles that are independent from each other.