I am building an android application and I am not sure about how to implement navigation in MVVM architecture.
The first approach I took is to bind the on click event button to a function in the ViewModel that does some logic if necessary(for example some data validation) and then fires a LiveData event(that tells the view to navigate to different screen) to a view that observes the ViewModel.
<button android:id="#+id/btnId"
android:onClick="#{(v) -> myViewModel.onSaveClick()}"
.../>
class MyViewModel : ViewModel() {
val saveNavigation = MutableLiveData<Event<Customer>>()
val errorMessage = MutableLiveData<Event<String>>()
fun onSaveClick() {
if (validateCustomer(customer)) {
repository.save(customer)
saveNavigation.value = Event(customer)
}
else
errorMessage.value = Event("error")
}
}
class View : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//observe event protects from re-reading the value on screen rotation
myViewModel.saveNavigation.observeEvent(this) {
findNavController().navigate(CustomerViewDirections
.actionCustomerInfoToCustomerBalanceHistory(it))
}
myViewModel.errorMessage.observeEvent(this) { toast(it) }
}
}
The second approach is that the view registers to an onClickListener and actively calls the ViewModel logic function(for example validation) and only after that the view does the navigation
class MyViewModel : ViewModel() {
fun save() : Status {
if (validateCustomer(customer)) {
repository.save(customer)
return Status.OK
}
else
return Status.Error //or some string message
}
}
class View : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
btnId.setOnClickListener {
if (myViewModel.save() == Status.OK)
findNavController().navigate(CustomerViewDirections
.actionCustomerInfoToCustomerBalanceHistory(myViewModel.customer))
else
toast("error")
}
}
}
Which of these two ways is better for the MVVM architecture or maybe there is another option to go with?
I would suggest you create a Navigator interface that gets injected to your ViewModels using your dependency injection of choice. You then implement that interface in a component that handles the navigation. You would then navigate using the methods from that Navigator interface.
For instance,
interface Navigator {
fun navigateToSettings()
}
class NavigatorImpl : Navigator {
override fun navigateToSettings() {
TODO()
}
}
Related
I want use one viewModel and observe object from one method. But I dont want write this observe method in all fragment. Only write in one place and use other fragments. I think I need fragment extension but can't get it how do this. I need help.
This viewModel that I want use.
SharedViewModel.kt
class SharedViewModel #Inject constructor(private val notificationServiceRepo: NotificationServiceRepo) : ViewModel() {
private val _helpNotification = SingleLiveEvent<NetworkResult<BaseResponse<Any>>>()
val helpNotification get() = _helpNotification
fun postHelpNotification(helpNotificationRequest: HelpNotificationRequest) = viewModelScope.launch (
Dispatchers.IO){
_helpNotification.postValue(NetworkResult.Loading)
_helpNotification.postValue(notificationServiceRepo.postNotificationHelp(helpNotificationRequest))
}
}
this is call method and observe function:
MainFragment.kt
viewModel.postHelpNotification(HelpNotificationRequest(0))
viewModel.helpNotification.observe(viewLifecycleOwner) {
it?.onLoading {}
it?.onSuccess { result->
result?.let {
InfoDialogWithOneText(
InfoDialogType.GOT_HELP_ASKING_FROM_STAFF
).show(childFragmentManager, InfoDialog.TAG)
}
}
it?.onError { error ->
InfoDialogWithOneText(
InfoDialogType.GOT_HELP_ASKING_FROM_STAFF
).show(childFragmentManager, InfoDialog.TAG)
}
}
}
Tried use sharedViewModel but I will have to write observe method for all.
Tried to baseViewModel but it get error hilt view model and also it will be same like shared view model.
For the abstract part, you want it need to cover everything - from having a view model that provides the observable event and handling it. I had to change some types because you did not provide them but it should not be too difficult to apply this to your case.
abstract class HelpNotificationFragment : Fragment() {
internal abstract val viewModel: HelpNotificationViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.helpNotification.observe(viewLifecycleOwner) { result ->
println(result)
}
}
}
abstract class HelpNotificationViewModel : ViewModel() {
private val _helpNotification = SingleLiveEvent<Result<Any>>()
// Specify the immutable type otherwise you would expose it as mutable
val helpNotification: LiveData<Result<Any>>
get() = _helpNotification
fun postHelpNotification(helpNotificationRequest: Result<Any>) {
viewModelScope.launch(Dispatchers.IO) {
_helpNotification.postValue(helpNotificationRequest)
}
}
}
And this is how you would implement the fragments that would use it - the overriding the view model will take care of forcing you to use view model with proper parent:
#HiltViewModel
class MainViewModel #Inject constructor(): HelpNotificationViewModel()
#AndroidEntryPoint
class MainFragment : HelpNotificationFragment() {
override val viewModel: MainViewModel by viewModels()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_main, container, false)
view.findViewById<Button>(R.id.button).setOnClickListener {
viewModel.postHelpNotification(Result.success("Yay!"))
}
return view
}
}
I am developing an android app using Jetpack library:
Hilt
Navigation
ViewModel
DataBinding
Actually, I am familiar with MVP pattern.
I am trying to study MVVP pattern (Databinding and Jetpack ViewModel)
I have 2 fragments (A and B).
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
#AndroidEntryPoint
class AFragment {
private val viewModel: AViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.viewModel = viewModel
with(binding) {
button.setOnClickListener {
this#AFragment.viewModel.doAction()
}
}
viewModel.result.observe(viewLifecycleOwner) { result ->
findNavController().navigate(AFragmentDirections.actionAFragmentToBFragment(result))
}
}
}
And here is AViewModel:
#HiltViewModel
class AViewModel #Inject constructor(): ViewModel() {
private val _result: MutableLiveData<Int> = MutableLiveData()
val result: LiveData<Int>
get() = _result
fun doAction() {
_result.postValue(SOME_ACTION_RESULT)
}
}
It shows BFragment correctly.
But If I touch Back Button on BFragment, it still shows BFragment.
Actually, It went to back AFragment, but it comes again to BFragment.
When I touch Back Button on BFragment,
AFragment is started again (I checked onViewCreated() is called again)
Below observe code is called again:
viewModel.result.observe(viewLifecycleOwner) { result ->
findNavController().navigate(AFragmentDirections.actionAFragmentToBFragment(result))
}
Why this code is called again?
And do I write code correctly?
What is the best practice?
Now, I found a solution.
In AFragment:
viewModel.result.observe(viewLifecycleOwner) { result ->
if (result != null) {
findNavController().navigate(AFragmentDirections.actionAFragmentToBFragment(result))
viewModel.resetResult()
}
}
and In AViewModel:
fun resetResult() {
_result.postValue(null)
}
With this code, it works fine.
Yes... But I don't like this code...
It's... so weird...
I don't know what is the best practice...
the problem is related with livedata and fragment lifecycle.
AFragment and AViewModel lives when you move to FragmentB, but view in AFragment detach and attach when you move and come back. It means onViewCreated() called every time when you touch Back button on BFragment. As a result, AFragment start to observe AViewModel which has already valid data with its _result livedata.
You should separate uidata and events in livedatas. Easiest solution is SingleEventLiveData implementation and use it.
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
in viewmodel:
private val _result: MutableLiveData<Event<Int>> = MutableLiveData()
val result: LiveData<Event<Int>>
get() = _result
fun doAction() {
_result.postValue(Event(5))
}
how to observe:
viewModel.result.observe(viewLifecycleOwner) { result ->
result.getContentIfNotHandled()?.let {
findNavController().navigate(R.id.action_fragment_a_to_fragment_b)
}
}
sources:
https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150
How to create LiveData which emits a single event and notifies only last subscribed observer?
I use Clean Architecture, LiveData, Navigation component & Bottom Navigation view.
I am creating a simple application with three tabs. By default, the First tab Fragment loads user data using some API. When i go to another tabs and then return to the First tab Fragment, i see, that observe return a new data!
I need observe not to return data again when I switch back to the first tab! what am I doing wrong? Could you help me please?
P.s. For navigation i use sample from navigation-advanced-sample and after switching tabs onDestroy is not called.
First solution in the article Observe LiveData from ViewModel in Fragment said:
One proper solution is to use getViewLifeCycleOwner() as LifeCycleOwer while observing LiveData inside onActivityCreated as follows.
I use following code, but it's not work for me:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
Timber.d("onActivityCreated")
viewModel.getProfileLive().observe(viewLifecycleOwner, observer)
}
Second solution in the article Architecture Components pitfalls — Part 1 recommends using Resetting an existing observer and Manually unsubscribing the observer in onDestroyView(). But it doesn't work for me either...
ProfileFragment.kt
class ProfileFragment : DaggerFragment() {
#Inject
lateinit var viewModel: ProfileFragmentViewModel
private val observer = Observer<Resource<Profile>> {
when (it.status) {
Resource.Status.LOADING -> {
Timber.i("Loading...")
}
Resource.Status.SUCCESS -> {
Timber.i("Success: %s", it.data)
}
Resource.Status.ERROR -> {
Timber.i("Error: %s", it.message)
}
}
};
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Timber.d("onCreate")
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Timber.d("onCreateView")
return inflater.inflate(R.layout.fragment_profile, container, false)
}
fun <T> LiveData<T>.reObserve(owner: LifecycleOwner, observer: Observer<T>) {
removeObserver(observer)
observe(owner, observer)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Timber.d("onViewCreated")
viewModel.getProfileLive().observe(viewLifecycleOwner, observer)
// viewModel.getProfileLive().reObserve(viewLifecycleOwner, observer)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
Timber.d("onActivityCreated")
}
override fun onDestroyView() {
super.onDestroyView()
Timber.d("onDestroyView")
// viewModel.getProfileLive().removeObserver(observer)
}
override fun onDestroy() {
super.onDestroy()
Timber.d("onDestroy")
}
override fun onDetach() {
super.onDetach()
Timber.d("onDetach")
}
}
ProfileFragmentViewModel.kt
class ProfileFragmentViewModel #Inject constructor(
private val profileUseCase: ProfileUseCase
) : ViewModel() {
init {
Timber.d("Init profile VM")
}
fun getProfileLive() = profileUseCase.getProfile()
}
ProfileUseCase
class ProfileUseCase #Inject constructor(
private val profileRepository: ProfileRepository
) {
fun getProfile(): LiveData<Resource<Profile>> {
return profileRepository.getProfile()
}
}
ProfileRepository.kt.
class ProfileRepository #Inject constructor(
private val loginUserDao: LoginUserDao,
private val profileDao: ProfileDao,
) {
fun getProfile(): LiveData<Resource<Profile>> =
liveData(Dispatchers.IO)
{
emit(Resource.loading(data = null))
val profile = profileDao.getProfile()
// Emit Success result...
}
}
It's because of how Fragment Lifecycle works. When you move to and fro from a fragment onViewCreated() is called again. In onViewCreated you're calling viewModel.getProfileLive() which returns the livedata upto from the repository and observe to it.
Since onViewCreated() gets called everytime when you move back to the Fragment so is your call to viewModel.getProfileLive() and in turn the repository gets called again which again triggers the observe method in your Fragment.
In order to solve this problem,
create a LiveData variable in your ViewModel, set it to the returned Live Data from Repository.
In the Fragment observe to the LiveData variable of your ViewModel not the one returned from Repository.
That way, your observe method will get triggered on very first time and only when value of your data from repository changes.
In my fragment, I have this code:
fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//...
viewModel.state.observe(viewLifecycleOwner) {
//do something
}
}
And in my ViewModel:
class MyViewModel: ViewModel() {
val state = liveData {
val state = dataSource.getState()
emit(state)
}
}
When I navigate to another fragment or activity, and press back button, Fragment's onCreateView and onViewCreated methods are called, but viewModel.state has the same value. I mean, dataSource.getState() is not called again. I need state to be re-fetched from data source.
Is this possible using liveData builder? If not, how should I do it?
You need just cal load function every time when it needed. One of possible way to do it
ViewModel :
val stateLiveData = MutableLiveData<>()
fun loadData() {
viewModelScope.launch {
val state = dataSource.getState()
stateLiveData.setValue(state)
}
}
Fragment :
fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//...
viewModel.loadData()
viewModel.stateLiveData.observe(viewLifecycleOwner) {
//do something
}
}
I am updating a LiveData value from a DialogFragment in the ViewModel, but not able to get the value in Fragment.
The ViewModel:
class OtpViewModel(private val otpUseCase: OtpUseCase, analyticsModel: IAnalyticsModel) : BaseViewModel(analyticsModel) {
override val globalNavModel = GlobalNavModel(titleId = R.string.otp_contact_title, hasGlobalNavBar = false)
private val _contactListLiveData = MutableLiveData<List<Contact>>()
val contactListLiveData: LiveData<List<Contact>>
get() = _contactListLiveData
private lateinit var cachedContactList: LiveData<List<Contact>>
private val contactListObserver = Observer<List<Contact>> {
_contactListLiveData.value = it
}
private lateinit var cachedResendOtpResponse: LiveData<LogonModel>
private val resendOTPResponseObserver = Observer<LogonModel> {
_resendOTPResponse.value = it
}
private var _resendOTPResponse = MutableLiveData<LogonModel>()
val resendOTPResponseLiveData: LiveData<LogonModel>
get() = _resendOTPResponse
var userSelectedIndex : Int = 0 //First otp contact selected by default
val selectedContact : LiveData<Contact>
get() = MutableLiveData(contactListLiveData.value?.get(userSelectedIndex))
override fun onCleared() {
if (::cachedContactList.isInitialized) {
cachedContactList.removeObserver(contactListObserver)
}
if (::cachedOtpResponse.isInitialized) {
cachedOtpResponse.removeObserver(otpResponseObserver)
}
super.onCleared()
}
fun updateIndex(pos: Int){
userSelectedIndex = pos
}
fun onChangeDeliveryMethod() {
navigate(
OtpVerificationHelpCodeSentBottomSheetFragmentDirections
.actionOtpContactVerificationBottomSheetToOtpChooseContactFragment()
)
}
fun onClickContactCancel() {
navigateBackTo(R.id.logonFragment, true)
}
fun retrieveContactList() {
cachedContactList = otpUseCase.fetchContactList()
cachedContactList.observeForever(contactListObserver)
}
fun resendOTP(contactId : String){
navigateBack()
cachedResendOtpResponse = otpUseCase.resendOTP(contactId)
cachedResendOtpResponse.observeForever(resendOTPResponseObserver)
}
}
The BaseViewModel:
abstract class BaseViewModel(val analyticsModel: IAnalyticsModel) : ViewModel() {
protected val _navigationCommands: SingleLiveEvent<NavigationCommand> = SingleLiveEvent()
val navigationCommands: LiveData<NavigationCommand> = _navigationCommands
abstract val globalNavModel: GlobalNavModel
/**
* Posts a navigation event to the navigationsCommands LiveData observable for retrieval by the view
*/
fun navigate(directions: NavDirections) {
_navigationCommands.postValue(NavigationCommand.ToDirections(directions))
}
fun navigate(destinationId: Int) {
_navigationCommands.postValue(NavigationCommand.ToDestinationId(destinationId))
}
fun navigateBack() {
_navigationCommands.postValue(NavigationCommand.Back)
}
fun navigateBackTo(destinationId: Int, isInclusive: Boolean) {
_navigationCommands.postValue(NavigationCommand.BackTo(destinationId, isInclusive))
}
open fun init() {
// DEFAULT IMPLEMENTATION - override to initialize your view model
}
/**
* Called from base fragment when the view has been created.
*/
fun onViewCreated() {
analyticsModel.onNewState(getAnalyticsPathCrumb())
}
/**
* gets the Path for the current page to be used for the trackstate call
*
* Override this method if you need to modify the path
*
* the page id for the track state call will be calculated in the following manner
* 1) analyticsPageId
* 2) titleId
* 3) the page title string
*/
protected fun getAnalyticsPathCrumb() : AnalyticsBreadCrumb {
return analyticsBreadCrumb {
pathElements {
if (globalNavModel.analyticsPageId != null) {
waPath {
path = PathElement(globalNavModel.analyticsPageId as Int)
}
} else if (globalNavModel.titleId != null) {
waPath {
path = PathElement(globalNavModel.titleId as Int)
}
} else {
waPath {
path = PathElement(globalNavModel.title ?: "")
}
}
}
}
}
}
The DialogFragment:
class OtpVerificationHelpCodeSentBottomSheetFragment : BaseBottomSheetDialogFragment(){
private lateinit var rootView: View
lateinit var binding: BottomSheetFragmentOtpVerificationHelpCodeSentBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
viewModel = getViewModel<OtpViewModel>()
binding = DataBindingUtil.inflate(inflater, R.layout.bottom_sheet_fragment_otp_verification_help_code_sent, container, false)
rootView = binding.root
return rootView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val otpViewModel = (viewModel as OtpViewModel)
binding.viewmodel = otpViewModel
otpViewModel.resendOTPResponseLiveData.observe(viewLifecycleOwner, Observer {
it?.let { resendOtpResponse ->
if(resendOtpResponse.statusCode.equals("000")){
//valid status code
requireActivity().toastMessageOtp(getString(R.string.otp_code_verification_sent))
}else{
//show the error model
//it?.errorModel?.let { it1 -> handleDiasNetworkError(it1) }
}
}
})
}
}
I am calling the resendOTP(contactId : String) method of the viewmodel from the xml file of the DialogFragment:
<TextView
android:id="#+id/verification_help_code_sent_resend_code"
style="#style/TruTextView.SubText2.BottomActions"
android:layout_height="#dimen/spaceXl"
android:gravity="center_vertical"
android:text="#string/verification_help_resend_code"
android:onClick="#{() -> viewmodel.resendOTP(Integer.toString(viewmodel.userSelectedIndex))}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/top_guideline" />
Now whenever I try to call resendOTPResponseLiveData from the Fragment it does not gets called:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d("OtpVerify" , "OnViewCreatedCalled")
viewModel.onViewCreated()
val otpViewModel = (viewModel as OtpViewModel)
binding.lifecycleOwner = this
binding.viewmodel = otpViewModel
binding.toAuthenticated = OtpVerifyFragmentDirections.actionOtpVerifyFragmentToAuthenticatedActivity()
binding.toVerificationBtmSheet = OtpVerifyFragmentDirections.actionOtpVerifyFragmentToOtpContactVerificationCodeSentBottomSheet()
otpViewModel.resendOTPResponseLiveData.observe(viewLifecycleOwner, Observer {
if(it?.statusCode.equals("000")){
//valid status code
requireActivity().toastMessageOtp(getString(R.string.otp_code_verification_sent))
}else{
//show the error model
it?.errorModel?.let { it1 -> handleDiasNetworkError(it1) }
}
})
}
So what wrong I am doing here.
EDIT
Basically I need clicklistener(resend button click) in dialogfragment, and need to read it in the fragment. So I used the concept of SharedViewModel.
So I make necessary changes in the ViewModel:
private val selected = MutableLiveData<LogonModel>()
fun select(logonModel: LogonModel) {
selected.value = logonModel
}
fun getSelected(): LiveData<LogonModel> {
return selected
}
In the DialogFragment:
otpViewModel.resendOTPResponseLiveData.observe(viewLifecycleOwner, Observer{
otpViewModel.select(it);
})
And in the fragment where I want to read the value:
otpViewModel.getSelected().observe(viewLifecycleOwner, Observer {
Log.d("OtpVerify" , "ResendCalled")
// Update the UI.
if(it?.statusCode.equals("000")){
//valid status code
requireActivity().toastMessageOtp(getString(R.string.otp_code_verification_sent))
}else{
//show the error model
it?.errorModel?.let { it1 -> handleDiasNetworkError(it1) }
}
})
But it is still not working.
Edit:
ViewModel Source for fragment:
viewModel = getSharedViewModel<OtpViewModel>(from = {
Navigation.findNavController(container as View).getViewModelStoreOwner(R.id.two_step_authentication_graph)
})
ViewModel Source for dialogfragment:
viewModel = getViewModel<OtpViewModel>()
Being new-ish to the Jetpack library and Kotlin a few months back I ran into a similar issue, if I understand you correctly.
I think the issue here is that you are retrieving you ViewModel using the by viewModels which means the ViewModel you get back will only be scoped to the current fragments context... If you would like to share a view model across multiple parts of your application they have to be activity scoped.
So for example:
//this will only work for the current fragment, using this declaration here and anywhere else and observing changes wont work, the observer will never fire, except if the method is called within the same fragment that this is declared
private val viewModel: AddPatientViewModel by viewModels {
InjectorUtils.provideAddPatientViewModelFactory(requireContext())
}
//this will work for the ANY fragment in the current activies scope, using this code and observing anywhere else should work, the observer will fire, except if the method is called fro another activity
private val patientViewModel: PatientViewModel by activityViewModels {
InjectorUtils.providePatientViewModelFactory(requireContext())
}
Notice my viewModel of type AddPatientViewModel is scoped to the current fragments context only via viewModel: XXX by viewModels, any changes etc made to that particular ViewModel will only be propagated in my current fragment.
Where as patientViewModel of type PatientViewModel is scoped to the activities context via patientViewModel: XXX by activityViewModels.
This means that as long as both fragments belong to the same activity, and you get the ViewModel via ... by activityViewModels you should be able to observe any changes made to the ViewModel on a global scope (global meaning any fragment within the same activity where it was declared).
With all the above in mind if your viewModel is correctly scoped to your activity and in both fragments you retrieve the viewModel using the by activityViewModels and updating the value being observed via XXX.postValue(YYY) or XXX.value = YYY you should be able to observe any changes made to the ViewModel from anywhere within the same activity context.
Hope that makes sense, it's late here, and I saw this question just before I hit the sack!
The problem is that you are actually not sharing the ViewModel between the Fragment and the Dialog. To share instances of a ViewModel they must be retrieved from the same ViewModelStore.
The syntax you are using to retrieve the ViewModels seems to be from a third party framework. I feel like probably Koin.
If that is the case, note that in Koin, getViewModel retrieves the ViewModel from the Fragment's own ViewModelStore. So, you are retrieving the ViewModel in your DialogFragment from its own ViewModelStore. On the other hand, in your Fragment, you are retrieving it using getSharedViewModel, in which you can specify which ViewModelStore it should retrieve the ViewModel from. So you are retrieving the ViewModel from two different ViewModelStores, and so, getting two different ViewModel. Interacting with one of those does not affect the other, as they are not the same instance.
To solve it, you should retrieve the ViewModel in both your Fragment and DialogFragment from the same ViewModelStore. For example, you could use getSharedViewModel in both, maybe specifying the same ViewModelStore manually at each, or even, without even specifying, which Koin will default to their Activity's one.
You could also even just use getViewModel in your Fragment, then pass its own specific ViewModelStore to the DialogFragment, in which you could then use getSharedViewModel, specifying the passed Fragment's ViewModelStore.