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:
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:
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)
}
}
At runtime, a FragmentManager can add, remove, replace, and perform other actions with fragments in response to user interaction. Each set of fragment changes that you commit is called a transaction, and you can specify what to do inside the transaction using the APIs provided by the FragmentTransaction class.
Various Android system operations can affect the state of your fragment. To ensure the user's state is saved, the Android framework automatically saves and restores the fragments and the back stack. Therefore, you need to ensure that any data in your fragment is saved and restored as well.
To detach an added Fragment from an Activity, you use: getFragmentManager(). beginTransaction(). detach(mFragment). commit().
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With