问题描述
我创建了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
}