Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Receiving NPE on binding when changing state of my request

I’m implementing an empty state screen of an app, but it is crashing after the second time it returns to the fragment.
When the app starts for the first time, it correctly creates my view, but when I add something to the previous list (main fragment -> register doc fragment (creating new item for my recycler list) -> main fragment), it crashes in the following line:

                    binding.imageEmptyState.hide()

From the following function:

private fun configDataCollector() = lifecycleScope.launch {
    viewModel.list.collectLatest { result->
        when(result) {
            is ResourceState.Success -> {
                binding.imageEmptyState.hide()
                binding.textEmptyState.hide()
                result.data?.let { values ->
                    adapterDoc.docs = values.toList()
                }
            }
            is ResourceState.Empty -> {
                binding.imageEmptyState.show()
                binding.textEmptyState.show()
            }
            else -> { }
        }
    }
}

Receiving:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

2023-01-04 11:55:35.905 14014-14014/com.tods.docreminder E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.tods.docreminder, PID: 14014
java.lang.NullPointerException
    at com.tods.docreminder.feature.doc.presentation.util.BaseFragment.getBinding(BaseFragment.kt:13)
    at com.tods.docreminder.feature.doc.presentation.doc.MainFragment.access$getBinding(MainFragment.kt:34)
    at com.tods.docreminder.feature.doc.presentation.doc.MainFragment$configDataCollector$1$1.invokeSuspend(MainFragment.kt:255)

My resource state class:

sealed class ResourceState<T>(
    val data: T? = null,
    val message: String? = null
) {
    class Success<T>(data: T?): ResourceState<T>(data)
    class Error<T>(message: String?, data: T? = null): ResourceState<T>(data, message)
    class Loading<T>: ResourceState<T>()
    class Empty<T>: ResourceState<T>()
}

Base fragment:

abstract class BaseFragment<VB: ViewBinding, VM: ViewModel>: Fragment() {
    private var _binding: VB? = null
    protected val binding get() = _binding!!
    protected abstract val viewModel: VM

    override fun onCreateView
                (inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        _binding = recoverViewBinding(inflater, container)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    protected abstract fun recoverViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB
}

Why would it happen in this situation?
Thanks for your help
If any else code is needed, I’ll edit the question asap.

>Solution :

A Fragment instance has a lifecycle that is longer than its views. As shown below, these functions represent lifecycles that start and stop. Views can be created and destroyed multiple times while the Fragment’s lifecycle is still running.

Fragment
  -onCreate
    -onCreateView   ** These two lines can be called multiple times
    -onDestroyView  ** inside the Fragment's outer lifecycle.
  -onDestroy

So if you reference binding in a coroutine that lives on the Fragment’s lifecycleScope instead of the viewLifecycle‘s lifecycleScope, then it’s possible that your coroutine will try to use binding when it is null, right at this point:

Fragment
  -onCreate
    -onCreateView
    -onDestroyView
    **Accessing binding here will throw an NPE because it is null after onDestroyView
  -onDestroy

If you use viewlifecycle.lifecycleScope for your coroutine, it will automatically be cancelled when the view is destroyed, so it will never try to use binding at these invalid times in the Fragment’s life.


It’s a matter of opinion, but I want to mention that I think BaseFragments cause more problems than they solve. Some articles about the subject:

Since Kotlin has extension functions and property delegates, you can get the same benefits without all the issues mentioned in the articles above.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading