More options for Android ViewModel

Is there a way to pass an additional argument to my AndroidViewModel custom constructor, apart from the 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 use the custom custom ViewModel class, I use this code in my fragment:

 MyViewModel myViewModel = ViewModelProvider.of(this).get(MyViewModel.class) 

Therefore, I do not know how to pass the optional String param argument to my custom ViewModel . I can pass only the application context, but not additional arguments. I would really appreciate any help. Thanks.

Edit: I added the code. Hope this is better now.

+47
source share
6 answers

You must 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 you instantiate the view model, you do this:

 MyViewModel myViewModel = ViewModelProviders.of(this, new MyViewModelFactory(this.getApplication(), "my awesome param")).get(MyViewModel.class); 
+111
source

For a single factory shared by several view models, I would extend mlyko's answer as follows:

 public class MyViewModelFactory extends ViewModelProvider.NewInstanceFactory { private Application mApplication; private Object[] mParams; public MyViewModelFactory(Application application, Object... params) { mApplication = application; mParams = params; } @Override public <T extends ViewModel> T create(Class<T> modelClass) { if (modelClass == ViewModel1.class) { return (T) new ViewModel1(mApplication, (String) mParams[0]); } else if (modelClass == ViewModel2.class) { return (T) new ViewModel2(mApplication, (Integer) mParams[0]); } else if (modelClass == ViewModel3.class) { return (T) new ViewModel3(mApplication, (Integer) mParams[0], (String) mParams[1]); } else { return super.create(modelClass); } } } 

And the instantiating view of the models:

 ViewModel1 vm1 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), "something")).get(ViewModel1.class); ViewModel2 vm2 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), 123)).get(ViewModel2.class); ViewModel3 vm3 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), 123, "something")).get(ViewModel3.class); 

With different types of models having different designers.

+8
source

I wrote a library that should make this task simpler and easier, without requiring multiple bindings or factory templates, while working with ViewModel arguments that can be provided by Dagger as dependencies: https://github.com/radutopor/ViewModelFactory

 @ViewModelFactory class UserViewModel(@Provided repository: Repository, userId: Int) : ViewModel() { val greeting = MutableLiveData<String>() init { val user = repository.getUser(userId) greeting.value = "Hello, $user.name" } } 

According to:

 class UserActivity : AppCompatActivity() { @Inject lateinit var userViewModelFactory2: UserViewModelFactory2 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_user) appComponent.inject(this) val userId = intent.getIntExtra("USER_ID", -1) val viewModel = ViewModelProviders.of(this, userViewModelFactory2.create(userId)) .get(UserViewModel::class.java) viewModel.greeting.observe(this, Observer { greetingText -> greetingTextView.text = greetingText }) } } 
0
source

(KOTLIN) My solution uses a bit of reflection.

Suppose you do not want to create the same Factory class every time you create a new ViewModel class that needs arguments. You can do this with Reflection.

For example, you will have two different activities:

 class Activity1 : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val args = Bundle().apply { putString("NAME_KEY", "Vilpe89") } val viewModel = ViewModelProviders.of(this, ViewModelWithArgumentsFactory(args)) .get(ViewModel1::class.java) } } class Activity2 : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val args = Bundle().apply { putInt("AGE_KEY", 29) } val viewModel = ViewModelProviders.of(this, ViewModelWithArgumentsFactory(args)) .get(ViewModel2::class.java) } } 

And ViewModels for these activities:

 class ViewModel1(private val args: Bundle) : ViewModel() class ViewModel2(private val args: Bundle) : ViewModel() 

Then the magic part, implementing the Factory class:

 class ViewModelWithArgumentsFactory(private val args: Bundle) : NewInstanceFactory() { override fun <T : ViewModel?> create(modelClass: Class<T>): T { try { val constructor: Constructor<T> = modelClass.getDeclaredConstructor(Bundle::class.java) return constructor.newInstance(args) } catch (e: Exception) { Timber.e(e, "Could not create new instance of class %s", modelClass.canonicalName) throw e } } } 
0
source

I made this class in which the already created object is transferred.

 private Map<String, ViewModel> viewModelMap; public ViewModelFactory() { this.viewModelMap = new HashMap<>(); } public void create(ViewModel viewModel) { viewModelMap.put(viewModel.getClass().getSimpleName(), viewModel); create(viewModel.getClass()); } @NonNull @Override public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { for (Map.Entry<String, ViewModel> viewModel : viewModelMap.entrySet()) { if (viewModel.getKey().equals(modelClass.getSimpleName())) { return (T) viewModel.getValue(); } } return null; } 

And then

 ViewModelFactory viewModelFactory = new ViewModelFactory(); viewModelFactory.create(new SampleViewModel(Args1, Args2)); SampleViewModel sampleViewModel = ViewModelProviders.of(this, viewModelFactory).get(SampleViewModel.class); 
0
source

Why not do it like this:

 public class MyViewModel extends AndroidViewModel { private final LiveData<List<MyObject>> myObjectList; private AppDatabase appDatabase; private boolean initialized = false; public MyViewModel(Application application) { super(application); } public initialize(String param){ synchronized ("justInCase") { if(! initialized){ initialized = true; appDatabase = AppDatabase.getDatabase(this.getApplication()); myObjectList = appDatabase.myOjectModel().getMyObjectByParam(param); } } } } 

and then use it in two steps:

 MyViewModel myViewModel = ViewModelProvider.of(this).get(MyViewModel.class) myViewModel.initialize(param) 
-1
source

Source: https://habr.com/ru/post/1271909/


All Articles