5

I have this Fragment that just serves as a splash screen while data is retrieved. The problem is that on a configuration change or if the Fragment is offscreen (user navigated out of the app) it crashes when it returns from the IO Coroutine block and tries to execute the navigation in the Main Coroutine block.

Here is the code:

Note: viewModel.repository.initData() makes a Retrofit call and persists the response to a Room database if data doesn't exist or is stale.

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

Also I need the navigation to take place only after the data is retrieved but the data retrieval has to be done on the IO thread and the navigation on the main thread.

I have been reading about scoping the Coroutine but I am still confused/unsure how it works and how to properly set it up.

nicoqueijo
  • 872
  • 2
  • 11
  • 28
  • 1
    You don't have to set it up yourself, just run the task in the `viewModelScope` inside the `LoadingViewModel`, using `viewModelScope.launch {`.. – EpicPandaForce Mar 16 '20 at 18:55
  • @EpicPandaForce I have tried that but can't get it to execute synchronously with the navigation. Meaning I only want to navigate after the data is initialized. – nicoqueijo Mar 16 '20 at 19:13
  • 1
    If you don't have multiple different coroutines going, you can just use the one that's already scoped to the Fragment's life, `lifecycleScope`. `viewModel.repository.initData()` should probably be a suspend function that correctly handles background work and updating the live data on the main thread. So there would be no reason to be messing with Dispatchers at all in the Fragment itself. – Tenfour04 Mar 16 '20 at 19:38
  • You probably don't even need a Repository and I have no idea why anyone wants to shoehorn them into their projects. You want ViewModelScope and possibly either a `LiveData>` or something that wasn't built on a completely unrelated abstraction and use https://github.com/Zhuinden/event-emitter instead – EpicPandaForce Mar 16 '20 at 21:04

1 Answers1

4

I was able to fix it by implementing something like this:

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)
                }
            }
        }
    }
}
nicoqueijo
  • 872
  • 2
  • 11
  • 28