Android ViewModel additional arguments
Is there a way to pass additional argument to my custom AndroidViewModel
constructor except Application context.
Example:
public class MyViewModel extends AndroidViewModel {
private final LiveData<List<MyObject>> myObjectList;
private AppDatabase appDatabase;
public MyViewModel(Application application, String param) {
super(application);
appDatabase = AppDatabase.getDatabase(this.getApplication());
myObjectList = appDatabase.myOjectModel().getMyObjectByParam(param);
}
}
And when I want to user my custom ViewModel
class I use this code in my fragment:
MyViewModel myViewModel = ViewModelProvider.of(this).get(MyViewModel.class)
So I don't know how to pass additional argument String param
into my custom ViewModel
. I can only pass Application context, but not additional arguments. I would really appreciate any help. Thank you.
Edit: I've added some code. I hope it's better now.
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.
Implement with Dependency Injection
This is more advanced and better for production code.
Dagger2, Square's AssistedInject offers a production-ready implementation for ViewModels that can inject necessary components such as a repository that handles network and database requests. It also allows for the manual injection of arguments/parameters in the activity/fragment. Here's a concise outline of the steps to implement with code Gists based on Gabor Varadi's detailed post, Dagger Tips.
Dagger Hilt, is the next generation solution, in alpha as of 7/12/20, offering the same use case with a simpler setup once the library is in release status.
Implement with Lifecycle 2.2.0 in Kotlin
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
}
}