0

Standard chat style app. Single-activity app with two fragments—one for the list of groups, another with a fragment to show the messages in the group. The two fragments are part of a nested nav graph so I can use navigation graph scope for my view models.

The ViewModel for the messages view needs the id of the messaging group so it can hit the API correctly. When the user taps one of the recycler view items on the message group list I attempt to create this ViewModel so the next fragment in the nav graph can use it:

// Try to create the ViewModel, with the custom factory so it can receive the message group id, on the destination fragment
val factory = MessagesViewModelFactory(item.group.id!!)
val chatVM: MessagesViewModel by navGraphViewModels(R.id.chat) { factory }

// Actually go to the next fragment, which is supposed to show all the messages for that group
val action = MessageGroupsFragmentDirections.actionNavigationSettingsToMessagesFragment()
this.findNavController().navigate(action)

In the onCreateView of the destination controller I do this to attempt to get the ViewModel:

val viewModel: MessagesViewModel by navGraphViewModels(R.id.chat)

which kills the app with this exception:

Caused by: java.lang.InstantiationException: > java.lang.Class<mycom.ui.messages.MessagesViewModel> has no zero argument constructor

Which is true, that constructor needs the message group id. However, that fragment (the messages one) does not know the message group id so I cannot create a new Factory... that's the whole reason I'm trying to instantiate it in the other fragment and reuse it in this fragment.

I guess my question is how am I supposed to retrieve a shared ViewModel, with a custom factory, in the destination fragment on the navigation graph?

Msencenb
  • 5,675
  • 11
  • 52
  • 84
  • Your navigation graph scoped ViewModel is going to live for the entire lifetime of that graph, so the second time you run your code, you'll just get back the previous ViewModel again (and not one with your new group ID at all). It doesn't sound like you want a shared ViewModel at all, but you just want some way for your messages screen's ViewModel to know what group was selected? – ianhanniballake Jun 27 '22 at 23:30
  • @ianhanniballake yeah that's correct. I was assuming I could create a new view model each time a new group was clicked that overwrote the old one (so tapping the same group wouldn't hit the API again). But at this point, whatever the best practice is to get which group was selected into the messages fragment/viewmodel... I'm on board with! – Msencenb Jun 27 '22 at 23:35
  • I've marked this as a duplicate of another question which talks about this same issue: you should be passing the selected group by using Safe Args. Getting your Safe Args argument into your ViewModel is already available via the `SavedStateHandle` parameter - no custom factory required. Your other issue (not hitting the API again) sounds like something your repository layer should be doing and not necessarily something ViewModel related at all (and something you should ask a separate question about if you need help with that side). – ianhanniballake Jun 28 '22 at 02:50
  • Thanks, I appreciate it. Hilt looks like exactly what I was looking for. Agreed on the API layer being a separate thing. – Msencenb Jun 28 '22 at 02:53
  • 1
    FWIW, the SavedStateHandle thing is not Hilt specific - it works just fine with the standard ViewModel factory too – ianhanniballake Jun 28 '22 at 03:02
  • Ahh thank you @ianhanniballake. I was able to get it working by simply accepting the savedStateHandle in the viewmodel constructor like you suggested. I had been reading the ViewModel overview docs and had completely missed the 'Saved State module for ViewModel' in the docs https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate – Msencenb Jun 28 '22 at 16:56

0 Answers0