Android 片段和 ViewModel 问题

Android Fragment and ViewModel issue

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

在 FragmentA 中,我正在进行 API 调用,在 API 调用成功后,我将 FragmentA 替换为 FragmentB 和 addToBackStack。

现在真正的问题开始了,当我在 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 的每个人来说,这在某些时候是一个普遍的问题。这里的问题是由 intentional LiveData 行为引起的:它 return 在您开始观察它时立即向您提供它存储的值(如果有的话)。 LiveData 主要设计用于 view/data-binding 解决方案。一旦 UI 组件正在观察 LiveData 它应该接收值并适当地显示它。所以你得到的行为是故意的。

包括我在内的许多其他开发人员都遇到过这个问题。我能够通过使用推荐的事件包装器来解决它,作为我在 this post 中发现的这个问题的解决方案。它简单易懂,并且按照 post.

中的描述工作

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

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

如果您想知道为什么一开始您会立即收到此 LiveData 的结果 - 这是因为您的视图模型已被缓存。当您使用 activity 作为视图模型商店或商店所有者 (ViewModelProvider(requireActivity())) 时,您的视图模型将继续存在,直到您使用的 activity 未被销毁。这意味着即使您通过按后退按钮返回到其前一个片段而离开 FragmentA,然后通过创建新实例 return 回到 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
}