I'm using lifecycle version 2.2.0-rc03 and the official docs and articles found don't even list the correct class name or constructor arguments. I think I have to get the ViewModel instance through something like this
viewModel = ViewModelProvider(this, SavedStateViewModelFactory(requireActivity().application, savedStateRegistryOwner))
.get(SelectedTracksViewModel::class.java)
but I can't figure out the SavedStateRegistryOwner.
Can someone give a simple example of how to create the saved state ViewModel instance and the correct way to save and restore a value in the ViewModel?
For using Saved State module for View Model you have to add the androidx.lifecycle:lifecycle-viewmodel-savedstate dependency to your project. This example has been written based on version 1.0.0-rc03.
Please add the following line to your project Gradle file:
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-rc03'
ViewModel implementation:
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() {
val liveData = state.getLiveData("liveData", Random.nextInt().toString())
fun saveState() {
state.set("liveData", liveData.value)
}
}
Activity implementation:
class SavedStateActivity : AppCompatActivity() {
lateinit var viewModel: SavedStateViewModel;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityStateBinding = DataBindingUtil.setContentView(this, R.layout.activity_state)
viewModel = ViewModelProvider(this, SavedStateViewModelFactory(this.application, this)).get(SavedStateViewModel::class.java)
binding.viewModel = viewModel
binding.lifecycleOwner = this
}
override fun onSaveInstanceState(outState: Bundle) {
if(::viewModel.isInitialized)
viewModel.saveState()
super.onSaveInstanceState(outState)
}
}
I have tested this code and it works fine.
I am adding an answer to this old post just in case someone might find it useful.
I managed to do it as follows:
Add the following dependency to your "build.gradle (Module: app)" file
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
Add savedState: SavedStateHandle property to the constructor of the ViewModel
class SelectedTracksViewModel(private val savedState: SavedStateHandle) : ViewModel() {
companion object {
private const val SAVED_TRACK_INDEX = "savedTrackIndex"
}
private var trackIndex: Int
set(value) {
field = value
// Simply update the savedState every time your saved property changes
savedState.set(SAVED_TRACK_INDEX, value)
}
init {
trackIndex = savedState.get<Int>(SAVED_TRACK_INDEX) ?: 0
}
fun moveToNextTrack() {
trackIndex++
// Initially I was updating savedState here - now moved to setter
// Some more code here
}
}
Finally in the activity/fragment
private val selectedTracksViewModel: SelectedTracksViewModel by lazy {
ViewModelProvider(this).get(SelectedTracksViewModel::class.java)
}
And that's it. No need for SavedStateViewModelFactory, simply add the savedState property to your ViewModel constructor and update it when tracked properties change. Everything else works as if you're not using savedState: SavedStateHandle and this way is very similar to the traditional onSaveInstanceState(Bundle) in activities/fragments.
Update: Initially I was updating savedState after changing trackIndex. This means one has to update savedState every time saved properties are changed. This is a huge potential future bug if one forgets to add that line. A better and more robust pattern is to update the savedState in the setter of the property.
As far as I understand you want to create View model with spec constructor.
You can use ViewModelProvider.Factory.
viewModel = ViewModelProvider(this, SavedStateViewModelFactory.create(state)
.get(SelectedTracksViewModel::class.java)
example of ViewModelFactory
public class SavedStateViewModelFactory {
public static <E> ViewModelProvider.Factory create(State state){
return new ViewModelProvider.Factory() {
#NonNull
#Override
#SuppressWarnings("unchecked")
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(SelectedTracksViewModel.class)) {
return (T) new SelectedTracksViewModel<>(state);
} else {
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
};
}
}
Related
I apologize if this has been asked before. I am trying to create multiple instances of the same type of viewmodel scoped to an activity using dagger-hilt, but even with different custom default args, it is returning the same instance each time.
I need all the viewmodel instances to be activity scoped, not fragment or navgraph scoped because I need all the fragments to subscribe to the updated data that will be received in the activity.
(Using Kotlin)
Activity Code
#AndroidEntryPoint
class Activity : AppCompatActivity() {
private val vm1:MyViewModel by viewModels(extrasProducer = {
val bundle = Bundle().apply {
putString("ViewModelType", "vm1")
}
MutableCreationExtras(defaultViewModelCreationExtras).apply {
set(DEFAULT_ARGS_KEY, bundle)
}
}) {
MyViewModel.Factory
}
private val vm2:MyViewModel by viewModels(extrasProducer = {
val bundle = Bundle().apply {
putString("ViewModelType", "vm2")
}
MutableCreationExtras(defaultViewModelCreationExtras).apply {
set(DEFAULT_ARGS_KEY, bundle)
}
}) {
MyViewModel.Factory
}
...
}
ViewModel Code
#HiltViewModel
class MyViewModel #Inject constructor(
application: Application,
private val myRepo: MyRepository,
private val savedStateHandle: SavedStateHandle
) : AndroidViewModel(application) {
...
// Define ViewModel factory in a companion object
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
val defaultArgs = extras[DEFAULT_ARGS_KEY]
println("extras $extras and default $defaultArgs")
// Get the Application object from extras
val application = checkNotNull(extras[APPLICATION_KEY])
// Create a SavedStateHandle for this ViewModel from extras
val savedStateHandle = extras.createSavedStateHandle()
savedStateHandle.keys().forEach {
println("factory $it, ${savedStateHandle.get<Any>(it)}")
}
return MyViewModel(
application = application,
myRepo = MyRepository(application),
savedStateHandle = savedStateHandle
) as T
}
}
}
}
When I print out the default arguments, the first initialized viewmodel is always returned, and is not initialized again even with both variables in the activity having different default arguments. Expected result: New viewmodel instance with different default arguments.
I think it has to do with the Viewmodel store owner key being the same, but I do want the viewmodel store owner to be the same, just as a new instance, if that makes sense.
I know that in the past you could use AbstractSavedStateViewModelFactory, or a custom viewmodel factory with ViewModelProvider.get(), but I can't access ViewModelProvider.get without passing a ViewModelStoreOwner, and since I don't want to pass it to the factory since it could leak the activity, I'm confused as to how to go about this. Is there a better way than using hilt to create multiple instances of the same type of viewmodel in the same scope?
override val viewModel: MyViewModel by activityViewModels()
Create instance of viewModel which lives with activity.
I've successfully implemented repository based MVVM. However I need to pass a class object between fragments. I've implemented a sharedViewModel between multiple fragments but the set value always gives null. I know this is due to me not passing the activity context to the initialization of the viewmodels in fragments. I am working with ModelFactory to make instances of my viewmodel yet I can't figure out a way to give 'applicationActivity()' .
Here's my modelFactory:
class MyViewModelFactory constructor(private val repository: MyRepository): ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(MyOwnViewModel::class.java)) {
MyOwnViewModel(this.repository) as T
} else {
throw IllegalArgumentException("ViewModel Not Found")
}
}
and this is how I intialize my viewmodel:
viewModel=ViewModelProvider(this, MyViewModelFactory(
MyRepository(MyServices() ) )).get(MyOwnViewModel::class.java)
fetching data and everything else works, but I need to be able to share data between fragments and i can't do that with this architecture. I'm not using dagger or Hilt.
Thank you for any pointers.
You can use by activityViewModels() and pass the factory
private val myViewModel: MyViewModel by activityViewModels(factoryProducer = {
MyViewModelFactory(<your respository instance>)
})
It would be good idea to get your repository instance from a singleton or from a field in Application class. If you choose to get from an Application class you can do it like this;
class MyApp: Application() {
val service by lazy { MyService() }
val repository by lazy { MyRepository(service) }
}
by defining them lazy, it ensures that they are not initialized until its necessary
With your application class, your viewmodel call should look like this
private val myViewModel: MyViewModel by activityViewModels(factoryProducer = {
MyViewModelFactory((activity?.application as MyApp).repository)
})
You can also write viewmodelfactory this way
class MyViewModelFactory(internal var viewModel: ViewModel) : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
return viewModel as T
}
}
And for share data between fragment you can use bundle
Can you tell me if my approach is right? It works but I don't know if it's correct architecture. I read somewhere that we should avoid calling viewmodel function on function responsible for creating fragments/activities mainly because of screen orientation change which recall network request but I really need to pass arguments from one viewmodel to another one. Important thing is I'm using Dagger Hilt dependency injection so creating factory for each viewmodel isn't reasonable?
Assume I have RecyclerView of items and on click I want to launch new fragment with details - common thing. Because logic of these screens is complicated I decided to separate single viewmodel to two - one for list fragment, one for details fragment.
ItemsFragment has listener and launches details fragment using following code:
fun onItemSelected(item: Item) {
val args = Bundle().apply {
putInt(KEY_ITEM_ID, item.id)
}
findNavController().navigate(R.id.action_listFragment_to_detailsFragment, args)
}
Then in ItemDetailsFragment class in onViewCreated function I receive passed argument, saves it in ItemDetailsViewModel itemId variable and then launch requestItemDetails() function to make api call which result is saved to LiveData which is observed by ItemDetailsFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//...
val itemId = arguments?.getInt(KEY_ITEM_ID, -1) ?: -1
viewModel.itemId = itemId
viewModel.requestItemDetails()
//...
}
ItemDetailsViewModel
class ItemDetailsViewModel #ViewModelInject constructor(val repository: Repository) : ViewModel() {
var itemId: Int = -1
private val _item = MutableLiveData<Item>()
val item: LiveData<Item> = _item
fun requestItemDetails() {
if (itemId == -1) {
// return error state
return
}
viewModelScope.launch {
val response = repository.getItemDetails(itemId)
//...
_item.postValue(response.data)
}
}
}
Good news is that this is what SavedStateHandle is for, which automatically receives the arguments as its initial map.
#HiltViewModel
class ItemDetailsViewModel #Inject constructor(
private val repository: Repository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
private val itemId = savedStateHandle.getLiveData(KEY_ITEM_ID)
val item: LiveData<Item> = itemId.switchMap { itemId ->
liveData(viewModelScope.coroutineContext) {
emit(repository.getItemDetails(itemId).data)
}
}
we should avoid calling viewmodel function on function responsible for creating fragments/activities mainly because of screen orientation change which recall network request
Yes, in your example a request will be executed whenever ItemDetailsFragment's view is created.
Take a look at this GitHub issue about assisted injection support in Hilt. The point of assisted injection is to pass additional dependencies at object's creation time.
This will allow you to pass itemId through the constructor, which then will allow you to access it in ViewModel's init block.
class ItemDetailsViewModel #HiltViewModel constructor(
private val repository: Repository,
#Assisted private val itemId: Int
) : ViewModel() {
init {
requestItemDetails()
}
private fun requestItemDetails() {
// Do stuff with itemId.
}
}
This way the network request will be executed just once when ItemDetailsViewModel is created.
By the time the feature is available you can either try workarounds suggested in the GitHub issue or simulate the init block with a flag:
class ItemDetailsViewModel #ViewModelInject constructor(
private val repository: Repository
) : ViewModel() {
private var isInitialized = false
fun initialize(itemId: Int) {
if (isInitialized) return
isInitialized = true
requestItemDetails(itemId)
}
private fun requestItemDetails(itemId: Int) {
// Do stuff with itemId.
}
}
I use SavedStateHandle to pass data between different components. My fragment use SavedStateViewModelFactory to initialize my VM (is it correct the initalization?) and a method in companion object to update the savedState.
My dependencies:
implementation "androidx.activity:activity-ktx:1.1.0"
implementation "androidx.fragment:fragment-ktx:1.2.4"
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0"
The ViewModel initalization in Fragment:
private val viewModel: MyViewModel by viewModels {
SavedStateViewModelFactory(activity?.applicationContext as Application, this)
}
Is it a good way to initialize ViewModel?
When I want to update my saved values I use this method:
companion object {
#JvmStatic
fun newInstance(cod: String, password: String) =
MyFragment().apply {
arguments = Bundle().apply {
putString(ARG_COD, cod)
putString(ARG_PASSWORD, password)
}
}
}
After called newInstance (that should update saved state), I run the method printCod in my ViewModel.
This is the VM:
class MyViewModel(
private val state: SavedStateHandle
) : ViewModel(){
fun getCurrentCod(): String {
return state.get(ARG_COD)?: ""
}
fun printCod() {
println(getCurrentCod())
}
companion object {
private val ARG_COD = "cod"
private val ARG_PASSWORD = "password"
}
}
My "main" (where code above is executed):
newInstance(userCode, userPass)
viewModel.printCod()
I spent two days understanding why printCod print always an empty string. would someone know how to help me by giving me advice or at least tell me where am i wrong?
I want to initialize ViewModel in Activity using androidx library
I have tried what documentation says but it is not working. the ".of" is not resolved.
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.example.myapplication.databinding.ActivityMainBinding`
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)
binding.setLifecycleOwner(this)
var model = ViewModelProvider.of(this).get(SheduleViewModel::class.java)
}
}
of is not resolved, maybe there are another way to do it in androidx
Updated answer:
Things changed a little bit, as the previously needed dependency - ViewModelProviders - got deprecated (see the old answer for details). You can now use the ViewModelProvider constructor directly.
So, in this case, the answer would be:
private val viewModel = ViewModelProvider(this).get(SheduleViewModel::class.java)
Note that, however, if you include the androidx.activity:activity-ktx:$Version dependency (a few of the commonly used AndroidX dependencies already include it for you), you can make use of property delegation:
private val viewModel: SheduleViewModel by viewModels()
Which internally will use ViewModelProvider and scope your ViewModel to your Activity. It's just a more concise way of writing the same thing. You can do the same for a Fragment by including the androidx.fragment:fragment-ktx:$Version dependency instead (again, commonly already included by other AndroidX dependencies).
Both the ViewModelProvider constructor and by viewModels() also accept a factory as a parameter (useful for injecting your ViewModel):
private val viewModel =
ViewModelProvider(this, viewModelFactory).get(SheduleViewModel::class.java)
and
private val viewModel: SheduleViewModel by viewModels { viewModelFactory }
Use the one that best suits you.
Old answer:
Add the androidx.lifecycle:lifecycle-extensions:$lifecycleExtensionsVersion dependency in order to import ViewModelProviders.
Updating ViewModel to Lifecycle Version 2.2.0 and Above
The ViewModels (VMs) may theoretically be initialized as class level instance variables using the Kotlin extension library import androidx.fragment.app.viewModels method by viewmodels(). By initializing the VM as a class level instance var it can be accessed within the class.
Question: Is there a downside to initializing the VMs as class level instance variables instead of inside onCreate?
When creating the VMs with the extension function inside onCreate the VMs are only scoped within onCreate and extra code is required to reassign the class level instance variables.
See documentation
ViewModel Overview
Lifecycle
Initialize VM as Class Instance Val
class Fragment : Fragment() {
private val viewModel: SomeViewModel by viewModels()
private fun observeViewState() {
viewModel.feedViewState.observe(viewLifecycleOwner) { viewState ->
//viewState used here.
}
}
}
Initialize VM in onCreate and Reassign Class Instance Var
class Fragment : Fragment() {
private lateinit var viewModel: SomeViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel: ContentViewModel by viewModels()
this.viewModel = viewModel
}
private fun observeViewState() {
viewModel.feedViewState.observe(viewLifecycleOwner) { viewState ->
//viewState used here.
}
}
}
Passing Arguments/Parameters
// Override ViewModelProvider.NewInstanceFactory to create the ViewModel (VM).
class SomeViewModelFactory(private val someString: String): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = SomeViewModel(someString) as T
}
class SomeViewModel(private val someString: String) : ViewModel() {
init {
//TODO: Use 'someString' to init process when VM is created. i.e. Get data request.
}
}
class Fragment: Fragment() {
// Create VM in activity/fragment with VM factory.
val someViewModel: SomeViewModel by viewModels { SomeViewModelFactory("someString") }
}
Enabling SavedState with Arguments/Parameters
class SomeViewModelFactory(
private val owner: SavedStateRegistryOwner,
private val someString: String) : AbstractSavedStateViewModelFactory(owner, null) {
override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, state: SavedStateHandle) =
SomeViewModel(state, someString) as T
}
class SomeViewModel(private val state: SavedStateHandle, private val someString: String) : ViewModel() {
val feedPosition = state.get<Int>(FEED_POSITION_KEY).let { position ->
if (position == null) 0 else position
}
init {
//TODO: Use 'someString' to init process when VM is created. i.e. Get data request.
}
fun saveFeedPosition(position: Int) {
state.set(FEED_POSITION_KEY, position)
}
}
class Fragment: Fragment() {
// Create VM in activity/fragment with VM factory.
val someViewModel: SomeViewModel by viewModels { SomeViewModelFactory(this, "someString") }
private var feedPosition: Int = 0
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
someViewModel.saveFeedPosition((contentRecyclerView.layoutManager as LinearLayoutManager)
.findFirstVisibleItemPosition())
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
feedPosition = someViewModel.feedPosition
}
}
For me, the only thing that worked:
implementation 'androidx.fragment:fragment:1.2.4'
PS. This is for someone who is using java and got stuck for a while like I did and this SO answer comes up in google all the time.
Apparently, the API has change as of this date (6 May 2020), I had to do this to get it working.
// 1. Create a ViewModel Class Let's call it AppStateViewModel
// 2. Put below code Inside Activity onCreate like this:
ViewModelProvider.Factory factory = new ViewModelProvider.NewInstanceFactory();
appStateManager = new ViewModelProvider(this, factory).get(AppStateViewModel.class);
ViewModelProviders: This class is deprecated. Use the constructors for ViewModelProvider directly.
Examples in Kotlin
This is how you can use ViewModelProvider directly:
If your view-model is extending AndroidViewModel with just one argument, the app - then you can use the default AndroidViewModelFactory and you don't have to write a new Factory. Example:
// Activity / fragment class
private lateinit var viewModel: MyOwnAndroidViewModel
// onCreate
viewModel = ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory(application)
).get(MyOwnAndroidViewModel::class.java)
If your view-model is only extending the ViewModel without extra arguments then use the NewInstanceFactory().
// Activity / fragment class
private lateinit var viewModel: MyOwnViewModel
// onCreate
viewModel = ViewModelProvider(
this,
ViewModelProvider.NewInstanceFactory()
).get(MyOwnViewModel::class.java)
Adam's answer above covers other variations as well.
Disclaimer: Still learning basic Android development - if there's any problem with the code, let me know in comments.
(How to) Use ViewModel from Android Architecture Component :
Add the Google Maven repository (Optional, just verify that)
Android Studio projects aren't configured to access this repository by default.
To add it to your project, open the build.gradle file for your project (not the ones for your app or module) and add the google() repository as shown below:
allprojects {
repositories {
google()
jcenter()
}
}
Declaring dependencies
Open your app-level build.gradle file,
Go to dependencies{} block
Put implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" for AndroidX version, $lifecycle_version here is latest version defined.
For Pre-AndroidX use implementation "android.arch.lifecycle:viewmodel:1.1.1" (1.1.1 is the last version from this artifact i guess.)
In your activity, use like this syntax
Import this class :
import androidx.lifecycle.ViewModelProviders; for AndroidX
import android.arch.lifecycle.ViewModelProviders; when using Pre-AndroidX
And obtain your ViewModel like following
ViewModelProviders.of(this).get(ProfileObservableViewModel::class.java) // Kotlin syntax
---- or ----
ViewModelProviders.of(this).get(ProfileObservableViewModel.class); // Java syntax
In your app gradle file make sure you have added below dependencies:
For Activity use:
implementation "androidx.activity:activity-ktx:1.4.1"
For Fragment use:
implementation 'androidx.fragment:fragment:1.4.1'
Paste the code below in build.gradle(:app)
implementation 'androidx.fragment:fragment-ktx:1.4.1'
paste the following or similar(relevant to your settings) in app.gradle under dependencies
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
I add the last version of this dependency from
https://developer.android.com/kotlin/ktx/extensions-list
implementation "androidx.activity:activity-ktx:1.4.0"