从 Fragment 返回时,Kotlin ViewModel onchange 被调用多次(使用生命周期实现)
Kotlin ViewModel onchange gets called multiple times when back from Fragment (using Lifecycle implementation)
我正在使用 MVVM 架构。
代码
当我点击一个按钮时,orderAction 方法被触发。它只是发布一个枚举(将添加更多逻辑)。
ViewModel
class DashboardUserViewModel(application: Application) : SessionViewModel(application) {
enum class Action {
QRCODE,
ORDER,
TOILETTE
}
val action: LiveData<Action>
get() = mutableAction
private val mutableAction = MutableLiveData<Action>()
init {
}
fun orderAction() {
viewModelScope.launch(Dispatchers.IO) {
// Some queries before the postValue
mutableAction.postValue(Action.QRCODE)
}
}
}
片段观察LiveData对象并调用打开新片段的方法。我在这里使用导航器,但我不认为有关它的详细信息在此上下文中有用。请注意,我正在使用 viewLifecycleOwner
片段
class DashboardFragment : Fragment() {
lateinit var binding: FragmentDashboardBinding
private val viewModel: DashboardUserViewModel by lazy {
ViewModelProvider(this).get(DashboardUserViewModel::class.java)
}
private val observer = Observer<DashboardUserViewModel.Action> {
// Tried but I would like to have a more elegant solution
//if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED)
it?.let {
when (it) {
DashboardUserViewModel.Action.QRCODE -> navigateToQRScanner()
DashboardUserViewModel.Action.ORDER -> TODO()
DashboardUserViewModel.Action.TOILETTE -> TODO()
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentDashboardBinding.inflate(inflater, container, false)
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.action.observe(viewLifecycleOwner, observer)
// Tried but still having the issue
//viewModel.action.reObserve(viewLifecycleOwner, observer)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
// Tried but still having the issue
//viewModel.action.removeObserver(observer)
}
private fun navigateToQRScanner() {
log("START QR SCANNER")
findNavController().navigate(LoginFragmentDirections.actionLoginToPrivacy())
}
}
问题
当我关闭打开的片段时(使用 findNavController().navigateUp()),DashboardFragment 的 Observe.onChanged 会立即被调用并再次打开片段。
我已经检查过这个 and tried all the proposed solutions in the mentioned link (as you can see in the commented code). Only 是否有效,但它不是很优雅并且迫使我每次都进行检查。
我想尝试更稳固的优化方案
请记住,在该线程中没有生命周期实现。
这就是 LiveData
的工作原理,它是一个值持有者,它持有最后一个值。
如果您需要使用您的对象,以便该操作仅触发一次,请考虑将您的对象包装在 Consumable
中,像这样
class ConsumableValue<T>(private val data: T) {
private val consumed = AtomicBoolean(false)
fun consume(block: ConsumableValue<T>.(T) -> Unit) {
if (!consumed.getAndSet(true)) {
block(data)
}
}
}
然后你定义你的LiveData为
val action: LiveData<ConsumableValue<Action>>
get() = mutableAction
private val mutableAction = MutableLiveData<ConsumableValue<Action>>()
那么在你的观察者中,你会做
private val observer = Observer<ConsumableValue<DashboardUserViewModel.Action>> {
it?.consume { action ->
when (action) {
DashboardUserViewModel.Action.QRCODE -> navigateToQRScanner()
DashboardUserViewModel.Action.ORDER -> TODO()
DashboardUserViewModel.Action.TOILETTE -> TODO()
}
}
}
更新
找到了 Frances 回答的不同但仍然有用的实现 here。看看
出现此问题是因为 LiveData 总是 post 向观察者提供可用数据(如果任何数据随时可用)。之后它将 post 更新。我认为这是预期的工作,因为即使问题跟踪器中出现错误,此行为仍未修复。
然而,开发人员在 SO 中提出了许多解决方案,我发现这个很容易适应并且实际上工作得很好。
解决方案
viewModel.messagesLiveData.observe(viewLifecycleOwner, {
if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED) {
//Do your stuff
}
})
我正在使用 MVVM 架构。
代码
当我点击一个按钮时,orderAction 方法被触发。它只是发布一个枚举(将添加更多逻辑)。
ViewModel
class DashboardUserViewModel(application: Application) : SessionViewModel(application) {
enum class Action {
QRCODE,
ORDER,
TOILETTE
}
val action: LiveData<Action>
get() = mutableAction
private val mutableAction = MutableLiveData<Action>()
init {
}
fun orderAction() {
viewModelScope.launch(Dispatchers.IO) {
// Some queries before the postValue
mutableAction.postValue(Action.QRCODE)
}
}
}
片段观察LiveData对象并调用打开新片段的方法。我在这里使用导航器,但我不认为有关它的详细信息在此上下文中有用。请注意,我正在使用 viewLifecycleOwner
片段
class DashboardFragment : Fragment() {
lateinit var binding: FragmentDashboardBinding
private val viewModel: DashboardUserViewModel by lazy {
ViewModelProvider(this).get(DashboardUserViewModel::class.java)
}
private val observer = Observer<DashboardUserViewModel.Action> {
// Tried but I would like to have a more elegant solution
//if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED)
it?.let {
when (it) {
DashboardUserViewModel.Action.QRCODE -> navigateToQRScanner()
DashboardUserViewModel.Action.ORDER -> TODO()
DashboardUserViewModel.Action.TOILETTE -> TODO()
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentDashboardBinding.inflate(inflater, container, false)
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.action.observe(viewLifecycleOwner, observer)
// Tried but still having the issue
//viewModel.action.reObserve(viewLifecycleOwner, observer)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
// Tried but still having the issue
//viewModel.action.removeObserver(observer)
}
private fun navigateToQRScanner() {
log("START QR SCANNER")
findNavController().navigate(LoginFragmentDirections.actionLoginToPrivacy())
}
}
问题
当我关闭打开的片段时(使用 findNavController().navigateUp()),DashboardFragment 的 Observe.onChanged 会立即被调用并再次打开片段。
我已经检查过这个
我想尝试更稳固的优化方案
请记住,在该线程中没有生命周期实现。
这就是 LiveData
的工作原理,它是一个值持有者,它持有最后一个值。
如果您需要使用您的对象,以便该操作仅触发一次,请考虑将您的对象包装在 Consumable
中,像这样
class ConsumableValue<T>(private val data: T) {
private val consumed = AtomicBoolean(false)
fun consume(block: ConsumableValue<T>.(T) -> Unit) {
if (!consumed.getAndSet(true)) {
block(data)
}
}
}
然后你定义你的LiveData为
val action: LiveData<ConsumableValue<Action>>
get() = mutableAction
private val mutableAction = MutableLiveData<ConsumableValue<Action>>()
那么在你的观察者中,你会做
private val observer = Observer<ConsumableValue<DashboardUserViewModel.Action>> {
it?.consume { action ->
when (action) {
DashboardUserViewModel.Action.QRCODE -> navigateToQRScanner()
DashboardUserViewModel.Action.ORDER -> TODO()
DashboardUserViewModel.Action.TOILETTE -> TODO()
}
}
}
更新
找到了 Frances 回答的不同但仍然有用的实现 here。看看
出现此问题是因为 LiveData 总是 post 向观察者提供可用数据(如果任何数据随时可用)。之后它将 post 更新。我认为这是预期的工作,因为即使问题跟踪器中出现错误,此行为仍未修复。 然而,开发人员在 SO 中提出了许多解决方案,我发现这个很容易适应并且实际上工作得很好。
解决方案
viewModel.messagesLiveData.observe(viewLifecycleOwner, {
if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED) {
//Do your stuff
}
})