首页 > 解决方案 > 如何将协程范围限定为片段,以便在片段离开屏幕或被销毁时自动取消?

问题描述

我有这个片段,它只是在检索数据时用作启动屏幕。问题在于,在配置更改或 Fragment 离开屏幕(用户导航出应用程序)时,它会在从 IO Coroutine 块返回并尝试在 Main Coroutine 块中执行导航时崩溃。

这是代码:

注意:viewModel.repository.initData()如果数据不存在或过时,则进行 Retrofit 调用并将响应持久保存到 Room 数据库。

class LoadingFragment : Fragment() {

    private lateinit var viewModel: LoadingViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_loading, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = ViewModelProvider(this).get(LoadingViewModel::class.java)
        CoroutineScope(Dispatchers.IO).launch {
            // Small delay so the user can actually see the splash screen
            // for a moment as feedback of an attempt to retrieve data.
            delay(250)
            try {
                viewModel.repository.initData()
                CoroutineScope(Dispatchers.Main).launch {
                    findNavController().navigate(R.id.action_loadingFragment_to_mainFragment)
                }
            } catch (e: IOException) {
                findNavController().navigate(R.id.action_loadingFragment_to_errorFragment)
            }
        }
    }
}

此外,我需要仅在检索数据后进行导航,但数据检索必须在 IO 线程上完成,并且在主线程上进行导航。

我一直在阅读有关确定协程范围的信息,但我仍然感到困惑/不确定它是如何工作的以及如何正确设置它。

标签: androidkotlinkotlin-coroutinesandroid-jetpack-navigation

解决方案


我能够通过实现这样的东西来修复它:

class LoadingFragment : Fragment() {

    private lateinit var viewModel: LoadingViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_loading, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = ViewModelProvider(this).get(LoadingViewModel::class.java)
        lifecycleScope.launch {
            withContext(Dispatchers.IO) {
                // Small delay so the user can actually see the splash screen
                // for a moment as feedback of an attempt to retrieve data.
                delay(250)
                try {
                    viewModel.initData()
                    withContext(Dispatchers.Main) {
                        findNavController().navigate(R.id.action_loadingFragment_to_mainFragment)
                    }
                } catch (e: IOException) {
                    findNavController().navigate(R.id.action_loadingFragment_to_errorFragment)
                }
            }
        }
    }
}

推荐阅读