viewmodel 函数可以有一个接收片段的参数吗?

Is it ok for a viewmodel function to have a parameter that receives a fragment?

我有一个用于用户输入的片段和一个用于处理验证和 IO 的视图模型。插入信息后,我 UI 导航到 'success' 对话框。现在这个导航发生在视图模型的 使用对片段的引用插入方法。这一切似乎都有效,但后来我想起视图模型不应该包含对片段或 activity 的引用。我的问题是这是否适用于视图模型函数的局部参数?它会以某种方式保留该参考吗?

ViewModel代码

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)
                }

        }
    }
}

ViewModel 和 Fragment 应该分开,永远不要将 Fragment 作为参数传递给 ViewModel。如果你想通过 ViewModel 触发 Fragment 动作,你应该在 Fragment 中使用 Observer。我将 post 一些我自己的代码片段来解释我的意思。应该注意的是,我在这里使用的是 Activity 而不是 Fragment,但概念保持不变。

在 ViewModel 中我创建了这些变量:

val closeInCall: LiveData<EmptyEvent>
    get() = _closeInCall

private val _closeInCall = MutableLiveData<EmptyEvent>()

当 ViewModel 中的某个方法被触发时,会发生这种情况:

_closeInCall.postValue(EmptyEvent())

在我的 Activity 中,我观察到 closeInCall 是这样的:

viewModel.closeInCall.observe(this, EventObserver {
    // Do stuff
})

我正在使用的 EmptyEvent 和 EventObserver 是 MVVM 助手 类 我喜欢在不需要接收 Fragment/Activity 中的任何值时使用,你不一定必须使用它们。

空事件:

/**
 * An [Event] without content.
 */
class EmptyEvent : Event<Unit>(Unit)

事件观察者:

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)
        }
    }
}

您可以尝试使用 lambda 函数来处理 ViewModel 中的 Activity/Fragment 中的 callback。如下所示:

    fun insertNewCustomer(name: String?,address: String?, city: String?, state: String?,
                                  phoneNumber: String?, emailAddress: String?, callBack: () -> Unit
    ){
        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)

                }
                callBack()
               /* withContext(Dispatchers.Main){
                    val dialogFragment = SuccessfullyAddedDialog()
                    val action = ManualAddCustomerFragDirections.
                    actionManualAddCustomerFragToSuccessfullyAddedDialog()
                    frag.findNavController().navigate(action)
                }*/

            }
        }
    }

添加从 Activity/Fragment 调用上述方法,如下所示:

        lifecycleScope.launch {
        yourViewModel.insertNewCustomer("name","address","city","state","phone","email"){
            val dialogFragment = SuccessfullyAddedDialog()
            val action = ManualAddCustomerFragDirections.
            actionManualAddCustomerFragToSuccessfullyAddedDialog()
            
            // If you are using binding, then use binding.root where binding is an instance variable instantiated in onCreateView() of Fragment
            binding.root.findNavController().navigate(action)
            // Or else you can get NavController from View
            
        }
    }