Why a Viewmodel Factory Is Needed in Android

Why a viewmodel factory is needed in Android?

We can not create ViewModel on our own. We need ViewModelProviders utility provided by Android to create ViewModels.

But ViewModelProviders can only instantiate ViewModels with no arg constructor.

So if I have a ViewModel with multiple arguments, then I need to use a Factory that I can pass to ViewModelProviders to use when an instance of MyViewModel is required.

For example -

public class MyViewModel extends ViewModel {
private final MyRepo myrepo;
public MyViewModel(MyRepo myrepo) {
this.myrepo = myrepo;
}
}

To instantiate this ViewModel, I need to have a factory which ViewModelProviders can use to create its instance.

ViewModelProviders Utility can not create instance of a ViewModel with argument constructor because it does not know how and what objects to pass in the constructor.

Viewmodel factory? is it something i need?

Your ViewModel takes in a repository and since then You have to use the ViewModelFactory. You can either use ViewModelProvider:

val factory = InjectorUtils.providePaperSellerViewModelFactory()
val viewModel = ViewModelProvider(
this,
factory)
.get(AuthViewModel::class.java)

or, as @IR42 has pointed out, You can use viewModels delegate:

val viewModel: AuthViewModel by viewModels { factory }

ViewModelFactory need

There are a few things that need to consider before using ViewModel and ViewModelFactory

  1. ViewModel is LifecycleAware Components.
  2. ViewModel survive configuration changes.
  3. ViewModelProvider' can only instantiateViewModel` with no-args contructor.

Why do we need to use factory pattern?

To instantiate ViewModel with arguments need to use ViewModelFactory. ViewModelProviders Utility can not create an instance of a ViewModel with argument constructor because it does not know how and what objects to pass in the constructor.

Also, you should follow the Dependency Injection principle. A class should not create dependencies it needs. It should be provided rather than creating.

For Example -

public class LogInViewModel extends ViewModel {
private final LogInRepo repo;
public LogInViewModel (LogInRepo repo) {
/* this.repo = new LogInRepo(); Not recommended, It violates DI principle*/
this.repo = repo;
}
}

Difference between by viewmodels and viewmodel creation using Factory?

If your ViewModel has a zero-argument constructor, or if it has a constructor where its only argument is of type Application and it's a subclass of AndroidViewModel, then you do not need a factory. (Or if your constructor is either of the above plus SavedStateHandle.) A view model factory is a class that is able to instantiate your ViewModel that has a more complicated constructor.

When instantiating your ViewModel without using a delegate, you have to use a lateinit var for the property because you can't instantiate it until onCreateView.

If your ViewModel had no need for a factory, the process of doing it without a delegate would look like this:

class WritingRoutineFragment : Fragment() {
private lateinit var viewModel: WriteRoutineViewModel

override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
//...
viewModel = ViewModelProvider(this, viewModelFactory).get(WriteRoutineViewModel::class.java)
//...
}
}

and if it did need a factory, it would look like this, where you have to instantiate a factory and pass it to the ViewModelProvider constructor:

class WritingRoutineFragment : Fragment() {
private lateinit var viewModel: WriteRoutineViewModel

override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
//...
viewModel = ViewModelProvider(this, WriteRoutineViewModelFactory()).get(WriteRoutineViewModel::class.java)
//...
}
}

The delegate allows you to do this more concisely in a val right at the declaration site so you don't have to do any setup of your view model property in onCreateView. It will lazily create the ViewModel the first time the property is used. The advantage is more concise and clearer code (lateinit var splits the property from its declaration and makes it mutable even though it will never change).

So the above code when no factory is needed looks like:

class WritingRoutineFragment : Fragment() {
private val viewModel: WriteRoutineViewModel by viewModels()
}

and if you do need a factory it will look like this. You pass it a function that instantiates the factory, which is easily done with a lambda:

class WritingRoutineFragment : Fragment() {
private val viewModel: WriteRoutineViewModel by viewModels { WriteRoutineViewModelFactory() }
}

The code in your example has an extra property just to hold the factory, which is an unnecessary complication since you'll never need to access it directly. It's also quite odd that the factory in your example has an empty constructor, because if the factory doesn't have any state, then it has no data to pass to the ViewModel constructor.

Do we really need viewModelFactories and viewmodelProviders when using Dagger?

Both ways work, so I was wondering which one is the right way to work, if using Dagger allows me to inject a viewmodel without needing a viewmodelfactory, is there a good reason to keep it?, or should I just get rid of this viewmodelfactory?

Both ways work differently. Try rotating your screen with stored data in your ViewModel and you'll see.

Dagger can create the ViewModel, which is what you make use of in that generic ViewModelFactory. Those view models should be unscoped, thus you'll be creating a new ViewModel every single time. The Android support library will cache that ViewModel and reuse it after rotation so that you can keep your data—the factory method gets called once and there will only ever be one ViewModel created (per lifecycle). You keep your data and everything behaves as expected.

If on the other hand you use Dagger to inject your ViewModel directly none of the above will apply. Like any other dependency, a new ViewModel will be injected on creation, leading to a ViewModel being created every single time it is used—you'll not only use the data stored in it, you won't be able to share state with fragments either.

Of course you could apply a scope to the ViewModel, but that scope should be longer lived than the Activity instance (to keep state between rotations), but no longer lived than the screen is visible. So you can neither scope it to the activity nor to the application lifecycle. You can get it to work by introducing a new scope in-between, but at this point you'd be reinventing the ViewModel library.


tl;dr Inject and use the factory or you'll get a confusing/wrong ViewModel implementation.

Why it is important to implement a ViewModelProvider.Factory for my viewModel class?

On its own, the ViewModel system only knows how to use a zero-argument constructor on your ViewModel subclasses (or a single-parameter constructor for AndroidViewModel subclasses). So, either:

  • Have no constructors at all, or

  • Have an explicit zero-argument constructor (but consider getting rid of your other one, as nothing should use it), or

  • Implement a factory, so you can call your desired custom constructor

Do you need to use a ViewModel Factory if not using dependency injection?

If you have a view model with constructor parameters you will have to write a factory for that. One way to avoid it is by removing those constructor paramters and setting these objects using methods (Not an adviced practice though).

Factory in your case will look like this,

public class CatalogViewModelFactory implements ViewModelProvider.Factory {
private Application application;
private RecyclerView.Adapter adapter;

public CatalogViewModelFactory (@NonNull Application application,
RecyclerView.Adapter adapter){
this.application = application;
this.adapter = adapter;

}
public <T extends ViewModel> T create(Class<T> modelClass) {
if (modelClass.isAssignableFrom(CatalogVerticalListViewModel.class)) {
return (T) new CatalogVerticalListViewModel(application,adapter);
}
throw new IllegalArgumentException("Unknown ViewModel class");
}
}

And use it in the activity,fragment... as

 CatalogViewModelFactory  factory =
new CatalogViewModelFactory (application,adapter);

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


Related Topics



Leave a reply



Submit