user9201277
user9201277

Reputation:

Hilt-Dagger ViewModel calling from Fragment

I'm using the ViewModel to update the title in the action bar

SharedViewModel

class SharedViewModel @ViewModelInject constructor(
    @Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    val title: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

    val backButton: MutableLiveData<Boolean> by lazy {
        MutableLiveData<Boolean>()
    }
}

MainActivity observer

@AndroidEntryPoint
...
sharedViewModel.title.observe(this, Observer {
    supportActionBar?.title = it
})

Using the code below seems to create a new instance in Fragment (checked in the debugger):

@AndroidEntryPoint
...
private val viewModel: SharedViewModel by viewModels()

But seems to work this way

val viewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)

Article reference

Is this supposed to be done this way or am I doing something wrong?

Thanks!

Upvotes: 2

Views: 6473

Answers (1)

Jenea Vranceanu
Jenea Vranceanu

Reputation: 4694

If you want to use a shared view model in a fragment you have to use by activityViewModels() instead of by viewModels().

Why does the next line work but by viewModels() doesn't?

ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)

Because by default viewModels() has its ownerProducer argument value as { this }. To understand it better here is the source code:

@MainThread
inline fun <reified VM : ViewModel> Fragment.viewModels(
    noinline ownerProducer: () -> ViewModelStoreOwner = { this },
    noinline factoryProducer: (() -> Factory)? = null
) = createViewModelLazy(VM::class, { ownerProducer().viewModelStore }, factoryProducer)

As you can see it is an extension function for Fragment class. It means that the ViewModelStoreOwner is the fragment. As soon as the fragment is removed from the stack all view models it stores in view model store are gone.

But if you use by activityViewModels() you use Activity as view model store owner. Notice use of requireActivity().viewModelStore instead of ownerProducer().viewModelStore which is a Fragment by default.

@MainThread
inline fun <reified VM : ViewModel> Fragment.activityViewModels(
    noinline factoryProducer: (() -> Factory)? = null
) = createViewModelLazy(VM::class, { requireActivity().viewModelStore },
    factoryProducer ?: { requireActivity().defaultViewModelProviderFactory })

Eventually, createViewModelLazy is calling

ViewModelProvider(store, factory).get(viewModelClass.java)

which is the equivalent of you creating view model by hand using

ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)

How to fix the issue?

Use by activityViewModels():

@AndroidEntryPoint
...
private val viewModel: SharedViewModel by activityViewModels()

Note: by activityViewModels() and by viewModels() are just lazy load implementations of the ViewModelProvider(...).get(...).

Upvotes: 10

Related Questions