【问题标题】:Kotlin ViewModel onchange gets called multiple times when back from Fragment (using Lifecycle implementation)从 Fragment 返回时,Kotlin ViewModel onchange 被多次调用(使用生命周期实现)
【发布时间】:2020-05-19 19:58:40
【问题描述】:

我正在使用 MVVM 架构。

代码

当我点击一个按钮时,orderAction方法被触发。它只是发布一个枚举(将添加更多逻辑)。

视图模型

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 obj 并调用打开新片段的方法。我在这里使用导航器,但我认为有关它的详细信息在这种情况下没有用处。请注意,我正在使用 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())
    }
}

问题

当我关闭打开的fragment时(使用findNavController().navigateUp()),立即调用DashboardFragment的Observe.onChanged,再次打开fragment。

我已经检查了这个question 并尝试了mentioned link 中提出的所有解决方案(正如您在注释代码中看到的那样)。只有this solution 有效,但它不是很优雅,并且每次都迫使我进行检查。

我想尝试一个更可靠、更优化的解决方案。

请记住,在该线程中没有生命周期实现。

【问题讨论】:

    标签: android kotlin mvvm fragment-oncreateview lifecycleowner


    【解决方案1】:

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

    【讨论】:

    • 它就像一个魅力!这个解决方案是优雅的、最佳的、可重用的和书呆子。这就是我要找的。谢谢!
    【解决方案2】:

    之所以会出现此问题,是因为如果有任何数据随时可用,LiveData 总是将可用数据发布给观察者。之后它将发布更新。我认为这是预期的工作,因为即使在问题跟踪器中提出了错误,这种行为也没有得到修复。 但是,SO 中的开发人员建议了许多解决方案,我发现这个很容易适应并且实际上工作得很好。

    解决方案

    viewModel.messagesLiveData.observe(viewLifecycleOwner, {
            if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED) {
                //Do your stuff
            }
        })  
    

    【讨论】:

    • 谢谢!这就是我要找的东西!
    【解决方案3】:

    更新

    找到了弗朗西斯回答 here 的不同且仍然有用的实现。看看

    【讨论】:

    • 这是 Google 使用的,所以,我会使用那个(有点非官方称为“SingleLiveData,它不会每次都触发,因为 liveData 只是有用的作为 UI 的价值持有者,尽管有很多东西无缘无故地支持它”:)
    • 是的,我从 Francesc 解决方案开始,然后迁移到 Google 解决方案。但我会保留 Francesc 解决方案作为官方答案,因为他驱使我采用这种 Google 方法
    猜你喜欢
    • 2020-08-16
    • 2021-07-29
    • 2017-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-14
    • 1970-01-01
    相关资源
    最近更新 更多