Search code examples
androidkotlinandroid-fragmentsmvvmkoin

Android, Can't access ViewModels from detached fragment


Every time I rotate my phone, it crashes and prints strange exception in SearchResultFragment no matter isDetached value is false:

java.lang.IllegalStateException: Can't access ViewModels from detached fragment

in this part of code:

private val searchViewModel: SearchViewModel by viewModel()

fun searchWord(word: String) {
        if(!isDetached) searchResultViewModel.searchWord(word)
}

which is called from another fragment SearchFragment:

searchResultFragment.searchWord(searchWord)

SearchResultFragment is added to SearchFragment in this way:

private val searchResultFragment = SearchResultFragment()

private fun addFragment() {
    val fragmentTransaction = childFragmentManager.beginTransaction()
    fragmentTransaction.add(R.id.searchContainer, searchResultFragment)
    fragmentTransaction.commit()
}

I'm using Koin to dependency injection. I'll be grateful for all hints.


Solution

  • It's hard to see exactly what's going on without the full context.

    The #1 problem I see (also mentioned by some other user(s) is that you appear to be calling a public method in a Fragment, from another Fragment. This is a red-flag, since you're now coupling two lifecycle independent objects with each other (among other things).

    If anything, the quick getaway from this is that you could instead rely on a shared view model that talks to both Fragments and can deliver the state you want.

    In any case, since it's hard to tell what is exactly going on, you could instead try something like this in your onCreateView(...)...

     if (savedInstanceState == null) {
                supportFragmentManager.commit {
                    replace(
                        R.id.searchContainer,
                        SearchResultFragment.newInstance(),
                        SearchResultFragment::class.java.simpleName
                    )
                }
            }
    

    newInstance() is implemented in SearchResultFragment as:

        companion object {
            fun newInstance() = SearchResultFragment()
        }
    

    Next, and because we don't know the full picture, you could instead try to see if Fragment Manager already has your fragment...

    Something like this (pseudo-code...)

            if (savedInstanceState == null) {
                val newFragment = supportFragmentManager.findFragmentByTag(SearchResultFragment::class.java.simpleName) ?: SearchResultFragment.newInstance()
                
                supportFragmentManager.commit {
                    replace(
                        R.id.simple_fragment_container,
                        newFragment,
                        SearchResultFragment::class.java.simpleName
                    )
                }
            }
    

    This way, you add the fragments with a Tag (just a String to identify them) and then you check if the fragment manager already has it and use that, instead of always creating a new instance.

    Last but not least, this could be affected by a number of other (external) factors:

    1. The Version of Fragment/Appcompat/AndroidX/etc. you're using (as you may be aware, Fragment Manager is not the best class in Android... so many fixes and changes happen all the time).

    2. The Android Version: Some Android APIs have had better "fragment luck" than others (especially before we had the separate artifacts to change the "fragment" versions).

    3. I don't think Koin has anything to add to this, but make sure you're at least using the latest stable.

    4. Perhaps the best you can do is rely on a ViewModel instead of all this if (!detached) {...} thing. You're really fighting the framework and ridding along the FragmentManager, something not even Google dares to do sometimes...