首页 > 解决方案 > DialogFragment 打开时未执行 RepeatOnLifecycle

问题描述

我有一个片段,它打开一个 DialogFragment,其中包含一些设置。当我更改 DialogFragment 中的设置时,我希望底层 Fragment 收集更改。我使用视图模型和 StateFlows 来存储设置并在 Fragment 中设置收集器,但是,它们永远不会被调用。但是视图模型已正确更新。是viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED)因为 DialogFragment 正在显示而没有调用收集器吗?但是背景片段仍然处于活动状态。

当我使用MutableLiveData代替MutableStateFlow并制作观察者而不是收集器时,它可以工作。所以它一定和repeatOnLifecycle有关。

class SomeFragment : Fragment() {
    // Dropped the other functions, e.g. onCreate, for simplicity

    val settings by activityViewModels()

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

        binding.testButton.setOnClickListener {
            val settingsFragment = SettingsDialogFragment()
            settingsFragment.show(requireActivity().supportFragmentManager, "settings")
        }

        lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                settings.lowerState.collect {
                    Log.e("DEBUG", "lowerState switched")
                }
                settings.upperState.collect {
                    Log.e("DEBUG", "upperState switched")
                }
            }
        }
    }
}
class SettingsViewModel(application: Application) : AndroidViewModel(application) {

    val lowerState: MutableStateFlow<Boolean> = MutableStateFlow(true)
    val upperState: MutableStateFlow<Boolean> = MutableStateFlow(true)
}

对话框片段是一个没有特殊功能的简单对话框

class SettingsDialogFragment : DialogFragment() {

    private var _binding: FragmentSettingsDialogBinding? = null
    private val binding get() = _binding!!

    private val settings: SettingsViewModel by activityViewModels()

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        _binding = FragmentRecordingSettingsDialogBinding.inflate(
            layoutInflater,
            null,
            false
        )
        _binding!!.settings = settings

        val builder = AlertDialog.Builder(requireActivity())

        return builder.setView(binding.root).create()
    }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="settings"
            type="com.example.SettingsViewModel"/>
    </data>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <androidx.appcompat.widget.SwitchCompat
                android:checked="@={settings.upperState}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
            <androidx.appcompat.widget.SwitchCompat
                android:checked="@={settings.lowerState}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>
</layout>

标签: androidkotlinandroid-fragments

解决方案


拿走了你的代码 - 只是我能够让它工作的准系统。您可以看到来自收集到的流的日志记录,这些记录SomeFragment由开关更改:

显示日志记录的测试应用程序

想法:

我假设通过使用委托by activityViewModels()来让你SettingsViewModel的两个 Fragment 有一个共同的 parent Activity,因此提供了相同的视图模型实例。

您的collect { .. }尾电话正在暂停,因此您只会让第一个收集器收集,在这种情况下:

settingsViewModel.lowerState
        .collect { .. suspending block in this coroutine nothing passed this will execute as FlowCollector still "collecting" and pushing into this block .. }

suspend CoroutineScope.() -> Unit一块中多个流的正确语法类似于:

 settingsViewModel.lowerState
        .onEach { Log.e("DEBUG", "lowerState switched") }
        .launchIn(this)
 settingsViewModel.upperState
        .onEach { Log.e("DEBUG", "upperState switched") }
        .launchIn(this)

这种方式launchIn()是尾调用的捷径scope.launch { collect() }- 这会启动一个没有suspend块的新协程 - 由onEach { .. }.

除此之外,除了可以使用您自己的准系统代码并尝试在一个新项目中将这个逻辑作为根本问题之外,没什么可说的。借助 Android 围绕范围/生命周期创建的所有“辅助”函数,它确实使该代码更难独立测试,因为它依赖于框架类和 LifeCycleOwner 回调,恕我直言,这使得它更难理解 - 但是..只是我的意见和一些我在复制和查看代码时想到了。


推荐阅读