I have a fragment for user input and a viewmodel to handle the validation and IO. Upon insertion of the info I have the UI navigate to a 'success' dialog. Right now this navigation happens from the viewmodel's insert method using a reference to the fragment. It all seems to work but then I remembered a viewmodel shouldn't hold a reference to a fragment or activity. My question is does that apply to a local parameter for a function of the viewmodel? Will it somehow hold on to that reference?
ViewModel code
suspend fun insertNewCustomer(name: String?,address: String?, city: String?, state: String?,
phoneNumber: String?, emailAddress: String?, frag: ManualAddCustomerFrag
){
if ( (validateName(name)
&& validateAddress(address)
&& validateCity(city)
&& validateState()
&& validateServiceDays())
&& isAddressTaken(address) == false
){
val customer = CustomerEntity(address!!,name,city,state,phoneNumber, emailAddress)
viewModelScope.launch {
customerRepo.insertCustomer(customer)
val days = convertSelectedDaysToStrings()
for (i in days.indices){
val serviceDay = ServiceDayEntity(0,customer.address,days[i])
customerRepo.addServiceDay(serviceDay)
}
withContext(Dispatchers.Main){
val dialogFragment = SuccessfullyAddedDialog()
val action = ManualAddCustomerFragDirections.
actionManualAddCustomerFragToSuccessfullyAddedDialog()
frag.findNavController().navigate(action)
}
}
}
}
The ViewModel and Fragment should be separated and one should never pass a Fragment as a parameter to a ViewModel. If you want to trigger a Fragment action by a ViewModel, you should use an Observer in the Fragment. I'll post some snippets of my own code to explain what I mean. It should be noted that I'm using an Activity here instead of a Fragment, but the concept remains the same.
In the ViewModel I created these variables:
val closeInCall: LiveData<EmptyEvent>
get() = _closeInCall
private val _closeInCall = MutableLiveData<EmptyEvent>()
When a certain method in the ViewModel gets triggered, this happens:
_closeInCall.postValue(EmptyEvent())
In my Activity I observe closeInCall
like this:
viewModel.closeInCall.observe(this, EventObserver {
// Do stuff
})
The EmptyEvent and EventObserver that I am using are MVVM helper classes I like to use when I don't need to receive any values in the Fragment/Activity, you don't necessarily have to use them.
EmptyEvent:
/**
* An [Event] without content.
*/
class EmptyEvent : Event<Unit>(Unit)
EventObserver:
import androidx.lifecycle.Observer
/**
* An [Observer] for [Event]s, simplifying the pattern of checking if
the [Event]'s content has
* already been handled.
*
* [onEventUnhandledContent] is *only* called if the [Event]'s
contents has not been handled.
*/
internal class EventObserver<T>(
private val onEventUnhandledContent: (T) -> Unit
) : Observer<Event<T>> {
override fun onChanged(event: Event<T>?) {
event?.getContentIfNotHandled()?.let { value ->
onEventUnhandledContent(value)
}
}
}