6

I am studying ViewModel to apply it to MVVM design pattern.

There was a method using by viemodels() and a method using ViewModelProvider.Factory in view model creation.

by viewModels() creates a ViewModel object.

ViewModelProvider.Factory also creates Viewmodel objects.

What is the difference between these two?

In addition, in some sample code, I saw the code in comment 3, which uses by viewModels() and factory together. What does this mean?

class WritingRoutineFragment : Fragment() {
    private val viewModel: WriteRoutineViewModel by viewModels() // 1
    private lateinit var viewModelFactory: WriteRoutineViewModelFactory

//  private val viewModel: WriteRoutineViewModel by viewModels(
//        factoryProducer = { viewModelFactory } // 3.What does this code mean?
//  )

    override fun onCreateView(inflater: LayoutInflater,
                              container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        _binding = FragmentWritingRoutineBinding.inflate(inflater, container, false)
        viewModelFactory = WriteRoutineViewModelFactory()
//        viewModel = ViewModelProvider(this, viewModelFactory).get(WriteRoutineViewModel::class.java) // 2
        return binding.root
    }
ybybyb
  • 1,385
  • 1
  • 12
  • 33
  • 3
    Does this answer your question? [How does kotlin use this by delegate to instantiate the viewmodel](https://stackoverflow.com/questions/58106707/how-does-kotlin-use-this-by-delegate-to-instantiate-the-viewmodel) – Pawel Jun 02 '21 at 18:36
  • @Pawel that helped a bit, but that link doesn't say what factoryproducer means. – ybybyb Jun 02 '21 at 18:46

1 Answers1

17

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.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • 2
    Adding to first point: you can also use default factory to handle one-arg `ViewModel`s (or two-arg `AndroidViewModel`s) that use [`SavedStateHandle`](https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate). – Pawel Jun 02 '21 at 20:53
  • Thank you. In summary, if the ` view model` does not need any `arguments`, there is no need for a `factory`, and i can create the `view model` using a `delegate` If the `viewmodel` needs some parameters, it's better to use a `factory`? – ybybyb Jun 03 '21 at 17:59
  • 1
    @Pawel, I thought I typed a thank you comment, but don't see it now. So, thanks. I added that to the answer. – Tenfour04 Jun 03 '21 at 18:00
  • 1
    @ybybyb, You can use the delegate regardless of whether you're using a factory or not. IMO you should always use the delegate because it's cleaner code. I showed examples with and without a factory. – Tenfour04 Jun 03 '21 at 18:02
  • @Tenfour04 Hmm I'm sorry but it's a bit difficult. You seem to always recommend using delegates (`by`) to create `viewmodels`. By the way, a `factory` is also creating a `view model`. So I don't understand this code. `by viewModels { ViewModelFactory() }` `by viewModels()` creates `viewmodels` object. By the way, `by viewModels { ViewModelFactory() }` is the code that creates the `view model` object the same? It creates the same viewmodel object, but is it because the code is concise? There is no `factory argument` in the example, but is it better to use `factory` if there is an argument? – ybybyb Jun 03 '21 at 19:13
  • `by viewModels()` doesn't create a ViewModel. It creates a ViewModel property delegate. The delegate creates the ViewModel, with or without an optionally supplied factory. My last block of code shows how to supply a factory to `by viewModels()`. – Tenfour04 Jun 03 '21 at 19:18