Android Fragment和ViewModel问题 事件包装器源代码

问题描述

我创建了FragmentA并在onViewCreated()中初始化了viewmodel。我已经用相同的方法附加了观察者。

在FragmentA中,我正在进行API调用,并且在API调用成功后,我正在使用addToBackStack用FragmentB替换FragmentA。

现在,真正的问题开始于当我按下FragmentB中的“后退”按钮时,调用了后堆栈中的FragmentA,但立即又被FragmentB取代。

class FragmentA : Fragment(){
private var viewmodel: Trackingviewmodel?=null

override fun onViewCreated(view: View,savedInstanceState: Bundle?) {
    super.onViewCreated(view,savedInstanceState)
    initviewmodel()
    attachObservers()
}

private fun initviewmodel(){
    viewmodel = viewmodelProvider(requireActivity()).get(Trackingviewmodel::class.java)
}

private fun attachObservers() {
    viewmodel?.mResult?.observe(viewLifecycleOwner,{
        it?.let { resource ->
            parseResource(resource)
        }
    })
}

//Called this method on Button CLick in UI
private fun validate(data:String){
    viewmodel?.coroutinesearch(data)
}

private fun parseResource(resource: Resource<GetsApiResponse>) {
    when (resource.status) {
        Status.SUCCESS -> {
            showLoading(false)
            //replaceFragmentWithBackStack is an Extension function
            replaceFragmentWithBackStack(FragmentB(),R.id.container)
        }
        Status.ERROR -> {
            showLoading(false)
            infoError(resource.responseCode)
        }
        Status.EXCEPTION -> {
            showLoading(false)
            infoException()
        }
        Status.LOADING -> {
            showLoading(true)
        }
    }
}

}

解决方法

对于使用LiveData的每个人来说,这在某些时候都是一个普遍的问题。这里的问题是由故意 LiveData行为引起的:它在您开始观察后立即将其存储的值(如果有)返回给您。 LiveData主要设计用于视图/数据绑定解决方案。 UI组件一观察到LiveData,就应该接收该值并适当地显示它。因此,您得到的行为是故意的。

包括我在内的许多其他开发人员都遇到了这个确切的问题。通过使用推荐的事件包装器来解决此问题,我可以在this post中找到它来解决此问题。它简单易懂,并且可以按照文章中的描述进行操作。

使用此事件包装器,您的观察者代码将更新为:

private fun attachObservers() {
    viewModel?.mResult?.observe(viewLifecycleOwner,{
        it?.getContentIfNotHandled()?.let { resource ->
            // Only proceed if the event has never been handled
            parseResource(resource)
        }
    })
}

如果您想知道为什么首先要立即收到来自LiveData的结果-这是因为视图模型已缓存。当您将活动用作视图模型商店或商店所有者(ViewModelProvider(requireActivity()))时,您的视图模型将一直存在,直到您使用的活动未被销毁为止。这意味着,即使您通过按上一个片段的后退按钮离开FragmentA,然后通过创建新实例返回到FragmentA,您也将获得相同的视图模型。

事件包装器源代码

/**
 * Used as a wrapper for data that is exposed via a LiveData that represents an event.
 */
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
        }
    }

    /**
     * Returns the content,even if it's already been handled.
     */
    fun peekContent(): T = content
}