Search code examples
androidkotlinandroid-viewmodelandroid-mvvm

Difference between by viewmodels and viewmodel creation using Factory?


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
    }

Solution

  • 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.