Search code examples
androidandroid-fragmentskotlinnavigationandroid-viewpager

Fragment's content gone when i navigate back to them


I'm using Navigation component in Single Activity app to make navigation , but i have strange Behavior with one fragment. It's simply to explain using images.

I have a fragment with ViewPager. ViewPager contains two another fragment, so it seems:

enter image description here

Bird - first fragment, Test - second. Bottom element is Bottom Navigation, it's not the part of the fragment. Fragment is between toolbar and Bottom Navigation.

This fragment, containing ViewPager, is not the start fragment, it's somewhere in the middle of stack.

So, when user click on bottom menu item, this navigation code running(from Main Activity):

 bottom_navigation.apply {
            itemIconTintList = null
            setOnNavigationItemSelectedListener { item ->
                when (item.itemId) {
                    R.id.about_bottom -> {
                        findNavController(R.id.host).navigate(R.id.toAboutUs)
                    }
                    R.id.error_bottom -> {
                        findNavController(R.id.host).navigate(R.id.toMessage)
                    }
                }
                true
            }
        }

where toMessage/toAboutUs are global points of another fragments.

So what the problem. When user click on bottom menu item, all working good. But when he pressed "back", content from fragments are gone. It' simply to see:

enter image description here

I can't even suggest the reason if this. I know that "main" Fragment and Fragments on ViewPager are not recreating, so why they lose content?

I'm not overriding the behavior of the back button anywhere. I just use app:defaultNavHost="true" of host fragment.


How data is transported: When user click button to open Fragment with ViewPager, data loaded from DB and save to ViewModel, only then user will be transport to this Fragment. When both child fragments created, they load data from ViewModel. And i have no place to the code where i clear ViewModel, so when user press back it's 100% my ViewModel contain something. But it's not displayed.

UPD: Spending some time, i realize that two "child" fragment are not recreating when i navigate back, but Main Fragment recreating. I think problem is about it, but still don't understand where exactly.

I need your help to understand what is going on.


Upd: provide some code of Fragments creating. BaseCompatFragment extends Fragment

MainFragment(container for another two Fragments):

class QuestionFragment : BaseCompatFragment() {

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        pager.adapter = QuestionViewPagerAdapter(fragmentManager!!)
        activity?.toolbar_title?.text = getString(R.string.title_question,1)

        layout_tab.apply {
            setupWithViewPager(pager)
            tabIconTint = null
            getTabAt(0)?.setIcon(R.drawable.ic_type_bird)
            getTabAt(1)?.setIcon(R.drawable.ic_hints)
        }
    }
}

QuestionViewPagerAdapter

    class QuestionViewPagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) {

        override fun getItem(position: Int): BaseCompatFragment {
            when (position) {
                0 -> return HintsFragment()
                1 -> return BaseInfoFragment()
            }
            return HintsFragment()
        }

        override fun getCount(): Int {
            return 2
        }
    }

HintsFragment(bird)

class HintsFragment : BaseCompatFragment(), HintsFragmentContract.View {

    @Inject
    lateinit var presenter: HintsFragmentPresenter

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        LibApp.get().injector.inject(this)
        presenter.attach(this)

        val animalWithHints =
            ViewModelProviders.of(activity!!).get(AnimalViewModel::class.java).getData().value

        val adapter = HintsAdapter(callback = { id ->
            //Some code will be here soon
        })
        //Do smth with content
        adapter.hintsList = animalWithHints?.animal?.hints?.split("///") as ArrayList<String>
        adapter.hintsStorage = animalWithHints.hints?.get(0) ?: Hints()

        recycler_hints.layoutManager = verticalManager(context)
        recycler_hints.adapter = adapter
    }
}

BaseInfoFragment(test)

class BaseInfoFragment : BaseCompatFragment() {

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val animalWithHints =
            ViewModelProviders.of(activity!!).get(AnimalViewModel::class.java).getData().value

        //A lot of logic to hide/show content, don't think it can be intresting

        val isBaseInfoOpen = animalWithHints?.hints?.get(0)?.baseInfoOpened == 1
        setBlockLayoutState(!isBaseInfoOpen)

        if (isBaseInfoOpen) {
            openContent(animalWithHints)
        } else {
            text_base_info.text = getString(R.string.base_info_price, 100)
            btn_base_info_positive.setOnClickListener {
                if (btn_base_info_negative.visibility == View.GONE) {
                    btn_base_info_negative.visibility = View.VISIBLE
                    text_base_info.text = getString(R.string.default_doubts)
                } else {
                    openContent(animalWithHints)
                }
            }

            btn_base_info_negative.setOnClickListener {
                it.visibility = View.GONE
                text_base_info.text = getString(R.string.base_info_price, 100)
            }
        }
    }

    private fun getRareIcon(rare: Int, context: Context): Drawable {
        return when (rare) {
            1 -> ContextCompat.getDrawable(context, R.drawable.ic_rare_fine)!!
            2 -> ContextCompat.getDrawable(context, R.drawable.ic_rare_medium)!!
            3 -> ContextCompat.getDrawable(context, R.drawable.ic_rare_bad)!!
            else -> ContextCompat.getDrawable(context, R.drawable.ic_warning)!!
        }
    }

    private fun getRareText(rare: Int): String {
        return when (rare) {
            1 -> getString(R.string.base_info_rare_1)
            2 -> getString(R.string.base_info_rare_2)
            3 -> getString(R.string.base_info_rare_3)
            else -> getString(R.string.base_info_rare_4)
        }
    }

    private fun setBlockLayoutState(state: Boolean) {
        base_info_closer.visibility = when {
            state -> View.VISIBLE
            else -> View.GONE
        }
        base_info_content.visibility = when {
            state -> View.GONE
            else -> View.VISIBLE
        }
    }

    private fun openContent(animalWithHints: AnimalWithHints?) {
        (img_closed_base_info.drawable as Animatable).start()
        rare_img.setImageDrawable(getRareIcon(animalWithHints?.animal?.rare ?: 0, activity!!))
        rare_text.text = getRareText(animalWithHints?.animal?.rare ?: 0)
        setBlockLayoutState(false)
    }
}

Solution

  • I think, problem is here: QuestionViewPagerAdapter(fragmentManager!!) You need to use getFragmentManager()/getSupportFragmentManager() when you add fragments directly on your activity. But when you need to add fragment on another fragment, you need to use getChildFragmentManager().

    From getSupportFragmentManager() documentation:

    Return the FragmentManager for interacting with fragments associated with this activity.

    So, it is the reason why your bird and test fragments did not recreate when their parent fragment did.

    And from getChildFragmentManager() doc:

    Return a private FragmentManager for placing and managing Fragments inside of this Fragment.

    This should do the trick. Hope it helps.