Android Viewmodel Additional Arguments

Android ViewModel additional arguments

You need to have a factory class for your ViewModel.

public class MyViewModelFactory implements ViewModelProvider.Factory {
private Application mApplication;
private String mParam;

public MyViewModelFactory(Application application, String param) {
mApplication = application;
mParam = param;
}

@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
return (T) new MyViewModel(mApplication, mParam);
}
}

And when instantiating the view model, you do like this:

MyViewModel myViewModel = ViewModelProvider(this, new MyViewModelFactory(this.getApplication(), "my awesome param")).get(MyViewModel.class);

For kotlin, you may use delegated property:

val viewModel: MyViewModel by viewModels { MyViewModelFactory(getApplication(), "my awesome param") }

There's also another new option - to implement HasDefaultViewModelProviderFactory and override getDefaultViewModelProviderFactory() with the instantiation of your factory and then you would call ViewModelProvider(this) or by viewModels() without the factory.

How to pass runtime arguments to a viewmodel?

I have finally used ViewModelProvider.Factory as follows:

class FilterByTypeViewModelFactory constructor(val dataManager: AppDataManager, 
val networkHelper: NetworkHelper,
val category: String) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return FilterByCategoryViewModel(dataManager , networkHelper , category) as T
}
}

The ViewModel now looks as follows:

class FilterByCategoryViewModel @ViewModelInject constructor(
val dataManager: AppDataManager,
val networkHelper: NetworkHelper,
val category: String
) : ViewModel() {
...
}

And in the Fragment:

@AndroidEntryPoint
class FilterByTypeFragment : Fragment() {
@Inject lateinit var dataManager: AppDataManager
@Inject lateinit var networkHelper: NetworkHelper
...
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (arguments != null) {
category = arguments?.get("categoryId") as String
val factory = FilterByTypeViewModelFactory(dataManager, networkHelper, category)
filterByCategoryViewModel = ViewModelProvider(requireActivity(), factory).get(FilterByCategoryViewModel::class.java)
}
setupUI()
setupObserver()
}
...
}

This is working for me. I have just been learning hilt. I was confused with the options like using ViewModelProvider.Factory or AssistedInject. Although this solution worked for me, I would like to know more about AssistedInject and the situations where we need to choose one over the other.

Pass arguments from fragment to viewmodel function

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)
}
}

How to pass arguments from Activity to ViewModel using Hilt (without a ViewModel Factory)

The trick is to initialize those variables only once, while the activity can be created multiple times. In my apps, I use a flag.

View model:

class CommentsViewModel : ViewModel() {

private var initialized = false

private var contentId : String? = null
private var highlightedCommentId : String? = null

fun initialize(contentId : String?, highlightedCommentId : String?) {
if (!initialized) {
initialized = true
this.contentId = contentId
this.highlightedCommentId = highlightedCommentId
}
}
//logic code here

}

Also, you should know that there is an open issue in dagger project exactly for this capability:
https://github.com/google/dagger/issues/2287

You're welcome to follow the progress.

How to pass custom parameters to a ViewModel using Factory?

I had this problem previously and I managed it by doing this. In your activity create your ViewModel factory as below :

//Inside MyActivity
ViewModelProvider.Factory factory = new ViewModelProvider.Factory() {
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new MyAndroidViewModel(getApplication(),
"My string!");
}
};

And then use it to create your ViewModel :

viewModel = ViewModelProviders.of(this, factory).get(MyAndroidViewModel.class); 

UPDATE:

As the ViewModelProviders class has been deprecated, the updated answer would be as below. The factory is the same as before, it has just changed to Kotlin.

var factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return SimpleAndroidViewModel(activity!!.application,
"My string!") as T
}
}

However, the line where we were creating the ViewModel instance has changed and now we are using the ViewModelProvider class.

private val viewModel: SimpleAndroidViewModel by lazy {
ViewModelProvider(this, factory).get(SimpleAndroidViewModel::class.java)
}

How do I pass an argument to the view model?

It's not that hard as it seems; just create a new public variable inside your ContentViewModel

Say

var isFavorite: Boolean = false

Then instead of init of a ViewModel, put your fetching logic inside a method.

fun getItems() {
_items.value = repository.items.filter { it.isFavorite == isFavorite }
}

And now call this method in fragment's onViewCreated() something like this:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView?.layoutManager = LinearLayoutManager(context)

adapter = MyItemModelsAdapter(this)
recyclerView?.adapter = adapter

val isFavorite = arguments?.getBoolean("onlyFavorites", false) ?: false
viewModel.isFavorite = isFavorite

viewModel.getItems()

viewModel.items.observe(this) {
adapter?.items = it
}
}

This way, you have the value for isFavorite before you actually call the getItems method.

I haven't tested this code, but I'm pretty sure it'll work.

Leave a comment if you need any more help with this.

Android different ways to create viewModel object which one to use when?

In case anyone looking for in depth answer, please check this, here we have the following way to create or get the viewModel object:

  1. val myViewModel1 = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MyViewModel::class.java)

  2. myViewModel2 = ViewModelProvider.AndroidViewModelFactory(this.application).create(MyViewModel::class.java)

  3. val myViewModel3 = ViewModelProvider(this).get(MyViewModel::class.java)

  4. val myViewModel4: MyViewModel by viewModels()

  5. val myViewModel5 by viewModels<MyViewModel>()

All do the same thing, the only two key differences is:

  1. The viewModel initialisation with lazy loading and without lazy loading.
  2. The viewModel with multiple parameter and no parameters.

Lets see this wrt the lazy loading and without lazy loading, the first three are without the delegate by that means there is no lazy loading of that object, so it's the developer
responsibility to create the viewModel object only when activity is created or the fragment is attached to the activity, that means the first three approach(1, 2, 3) can't be
used at global scope, if used at global scope the variable must be
a var with lateint or null initialisation, and the
initialisation(approach 1, 2, 3) must happen in the onCreate or
onViewCreated(in case of fragment).

Therefor the best way to create the viewModel object is using the delegate by(4, 5), both are same with a bit different syntax, I choose 4 because of it's simplicity and readability.

val myViewModel4: MyViewModel by viewModels()

The by delegate gives the flexibility to lazy load the instance and you can define the viewModel at global scope and get ride off the boilerplate code, if you try to initialise the viewModel at global scope without the delegate the app will crash since the viewModel will try to initialise before the activity is created(it will not lazy load the viewModel instance).

Now let's see how to lazy load with multiple parameters, the 6th approach not mention in the question.

If you have multiple parameters in your view model and not using any dependency injection, you can use a ViewModelFactory implementation and then lazy load it:

val myViewModelWithParm: MyViewModel by viewModels { MyViewModelFactory(application, "param1", "param2") }

ViewModelFactory implementation:

    class MyViewModelFactory(val application: Application, val param1: String, val param2: String) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MyViewModel(application, param1, param2) as T
}
}

Till this point we are clear on the delegate initialisation(4, 5), and how it is different with(1, 2, 3) now let's see the difference on the top 3 approach(1, 2, 3).

Let's first check 1 and 2.

  1. val myViewModel1 = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MyViewModel::class.java)
  2. myViewModel2 = ViewModelProvider.AndroidViewModelFactory(this.application).create(MyViewModel::class.java)

The key difference in them is one uses ViewModelProvider.NewInstanceFactory and other uses ViewModelProvider.AndroidViewModelFactory, so I checked the source code of both the classes and found that ViewModelProvider.AndroidViewModelFactory is actually the implementation of ViewModelProvider.NewInstanceFactory which override the create function that means both are doing the same stuff, preferable both approach should be chosen if we want multiple parameters however for that we have to override ViewModelProvider.NewInstanceFactory to create our own factory like it's done here

Now comes the third one:

val myViewModel3 = ViewModelProvider(this).get(MyViewModel::class.java)

This is the simple form of 1 and 2 when we don't have multiple parameters in our ViewModel and don't want to lazy load the object.

Note: I highly recommend the approach 4 or 5(both are same with different syntax), since this is the most suitable and optimal to write, if you don't have multiple arguments, in case you have multiple arguments you can use the approach 6 mentioned in the answer by implementing ViewModelProvider.Factory.



Related Topics



Leave a reply



Submit