Fragment navigation onBackPRessed - android

My task is to dynamically go on back pressed when i want to go back from fragment.
I have bottom navigation which is in activity.
My Code:
MainActivity:
setCurrentFragment(topArticlesFragment)
binding.bottomNavigationView.setOnItemSelectedListener {
when (it.itemId) {
R.id.topArticles -> setCurrentFragment(topArticlesFragment)
R.id.allArticles -> setCurrentFragment(everythingArticlesFragment)
}
return#setOnItemSelectedListener true
}
private fun setCurrentFragment(fragment: Fragment) {
supportFragmentManager
.beginTransaction().apply {
replace(R.id.frame_layout, fragment)
this.addToBackStack(null)
commit()
}
}
TopArticlesFragment:
binding.recyclerViewTop.visibility = GONE
binding.searchView.visibility = GONE
parentFragmentManager
.beginTransaction()
.replace(R.id.topArticlesFragment, fragment)
.addToBackStack(null)
.commit()
}
DetailsFragment:
binding.toolbar.setNavigationOnClickListener {
activity?.onBackPressed()
}
Pictures of what is happening
I also tried with nav_graph but i put bottom navigation in MainFragment and when i choose another fragment bottom navigation disappeared.
Edit:
EDIT:
I also have onResume and onStop in TopArticleFragment and Everything article fragment to show and hide topMenu and bottom navigation. But when used binding.recyclerView.isVisible = true nothing happens.
override fun onResume() {
super.onResume()
(activity as AppCompatActivity?)!!.supportActionBar!!.show()
(activity as AppCompatActivity?)!!.findViewById<BottomNavigationView>(R.id.bottomNavigationView).isVisible =
true
}
override fun onStop() {
super.onStop()
(activity as AppCompatActivity?)!!.supportActionBar!!.hide()
}

Related

lifecycle between Parent Fragment and Child Fragment

All the Fragment inherits from BaseFragment.
And I'd like to give back press event respectively to each fragment. But BaseFragment has default back press event like this.
override fun onResume() {
super.onResume()
logd("onResume() BaseFragment")
requireActivity().onBackPressedDispatcher.addCallback(this, backPressCallback)
}
And also the child fragments have
override fun onResume() {
super.onResume()
logd("onResume() ChildFragment")
requireActivity().onBackPressedDispatcher.addCallback(this, backPressCallback)
}
It will print,
onResume() BaseFragment
onResume() ChildFragment
So, the ChildFragment override and when I press back button, ChildFragment's backPressCallback is called.
However, when I go out and come back, the order is different.
onResume() ChildFragment
onResume() BaseFragment
So, the user sees ChildFragment but BaseFragment's backPressCallback is called. And it behaves different from what I expected. (ex. I want popBackStack but close the app)
How can I solve this problem? Or is there any method that called after the parent fragment is called?
According to this article, BaseFragment must be called before ChildFragment. But it doesn't seem so.
activity?.onBackPressedDispatcher?.addCallback(
viewLifecycleOwner,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
try {
if (!isChildFragmentOpen)
{
val trans: FragmentTransaction =
parentFragmentManager.beginTransaction()
trans.remove(Fragment())
trans.commit()
}else{
parentFragmentManager.popBackStack()
}
} catch (e: Exception) {
e.message
}
}
})
add this to your fragment.

Fragment popped call OnViewCreated after PopBackStack

i have 1 Activity with 3 Fragments. (A, B and C). So,
Activity -> FragmentContainerView with fragment A
<androidx.fragment.app.FragmentContainerView
android:id="#+id/host_fragment"
android:name="cl.gersard.shoppingtracking.ui.product.list.ListProductsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="ListProductsFragment" />
Fragment A has a button to go to Fragment B
Fragment A -> Fragment B (with addToBackStack)
Then, i go to from Fragment B to Fragment C
Fragment B -> Fragment C (without addToBackStack)
i need when i save a item in Fragment C, come back to Fragment A, so i dont use addToBackStack.
The problem is when in Fragment C i use
requireActivity().supportFragmentManager.popBackStack()
or
requireActivity().onBackPressed()
the Fragment A appears but the method OnViewCreated in Fragment C is called so execute a validations that i have in that Fragment C.
I need from Fragment C come back to Fragment A without calling OnViewCreated of Fragment C
Code of interest
MainActivity
fun changeFragment(fragment: Fragment, addToBackStack: Boolean) {
val transaction = supportFragmentManager.beginTransaction()
.replace(R.id.host_fragment, fragment,fragment::class.java.simpleName)
if (addToBackStack) transaction.addToBackStack(null)
transaction.commit()
}
Fragment A (ListProductsFragment)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
observeLoading()
observeProducts()
viewModel.fetchProducts()
viewBinding.btnEmptyProducts.setOnClickListener { viewModel.fetchProducts() }
viewBinding.fabAddPurchase.setOnClickListener { addPurchase() }
}
private fun addPurchase() {
(requireActivity() as MainActivity).changeFragment(ScanFragment.newInstance(),true)
}
Fragment B (ScanFragment)
override fun barcodeDetected(barcode: String) {
if (processingBarcode.compareAndSet(false, true)) {
(requireActivity() as MainActivity).changeFragment(PurchaseFragment.newInstance(barcode), false)
}
}
Fragment C (PurchaseFragment)
private fun observePurchaseState() {
viewModel.purchasesSaveState.observe(viewLifecycleOwner, { purchaseState ->
when (purchaseState) {
is PurchaseSaveState.Error -> TODO()
is PurchaseSaveState.Loading -> manageProgress(purchaseState.isLoading)
PurchaseSaveState.Success -> {
Toast.makeText(requireActivity(), getString(R.string.purchase_saved_successfully), Toast.LENGTH_SHORT).show()
requireActivity().supportFragmentManager.popBackStack()
}
}
})
}
The full code is here https://github.com/gersard/PurchaseTracking
OK, I think I see your issue. You are conditionally adding things to the backstack which put the fragment manager in a weird state.
Issue
You start on Main, add the Scanner to the back stack, but not the Product. So when you press back, you're popping the Scanner off the stack but the Product stays around in the FragmentManager. This is why get a new instance each and every time you scan and go back. Why this is happening is not clear to me - seems like maybe an Android bug? You are replacing fragments so it's odd that extra instances are building up.
One Solution
Change your changeFragment implementation to conditionally pop the stack instead of conditionally adding things to it.
fun changeFragment(fragment: Fragment, popStack: Boolean) {
if (keepStack) supportFragmentManager.popBackStack()
val transaction = supportFragmentManager.beginTransaction()
.replace(R.id.host_fragment, fragment,fragment::class.java.simpleName)
transaction.addToBackStack(null) // Always add the new fragment so "back" works
transaction.commit()
}
Then invert your current logic that calls changeFragment:
private fun addPurchase() {
// Pass false to not pop the main activity
(requireActivity() as MainActivity)
.changeFragment(ScanFragment.newInstance(), false)
}
And ...
override fun barcodeDetected(barcode: String) {
if (processingBarcode.compareAndSet(false, true)) {
// Pass true to pop the barcode that's currently there
(requireActivity() as MainActivity)
.changeFragment(PurchaseFragment.newInstance(barcode), true)
}
}

Handling Fragments onBackPress inside Activity

I have an Activity B that extends AppCompatActivity
I add (not replace) two fragments and also add them to the back stack
Activity B
Fragment 1
Fragment 2
supportFragmentManager
.beginTransaction()
.addToBackStack(null)
.add(containerId, fragment, fragment::class.java.simpleName)
.commit()
My Activity's onBackPress():
override fun onBackPressed() {
if (fragmentBackStackCount == 1) {
super.onBackPressed()
} else {
supportFragmentManager.popBackStack()
}
}
I want to go back to Activity A but I face a blank white screen (Activity B)
How can I handle fragments back stack correctly?
Try this code
override fun onBackPressed() {
if (fragmentBackStackCount <= 1) {
finish()
}
else {
super.onBackPressed()
}
}
Open Fragment with tag
supportFragmentManager.beginTransaction().addToBackStack(tag)
.add(R.id.flContainer, fragment, tag)
.commitAllowingStateLoss()
and Activity onBackPressed remove fragment
override fun onBackPressed() {
val secondFragment =
supportFragmentManager.findFragmentByTag("YoursecondFragmentTagName")
if (secondFragment != null){
// Here remove all fragment
if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStack(null,
FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
}else
super.onBackPressed()
}
Hope this help you...

How to return previous fragment by calling activity's onBackPress()

I have an activity with two fragments added to it using :
fun addFragment(fragment: Fragment){
private val fragmentManager = supportFragmentManager
val fragmentTag = fragment::class.simpleName.toString()
val ft = fragmentManager.beginTransaction()
ft.add(R.id.fragments_container, fragment,fragmentTag)
ft.addToBackStack(fragmentTag)
ft.commit()
}
I add two fragments A and B to activity with this order:
A ---> B
when i press back button on phone it return from B to A as expected
but the problem is that when i call activity's onBackPressed method when clicking on a view for example:
imgBack.setOnClickListener {
onBackPressed()
}
it does not work like when i press back button on the phone
it returns to fragment A but not showing fragment A views as expected.
onBackPressed:
override fun onBackPressed() {
if (fragmentManager.backStackEntryCount > 1) {
fragmentManager.popBackStack()
} else {
finish()
}
}
if you imgBack are in fragmentB call
imgBack.setOnClickListener {
activity?.onBackPressed()
}

Context is null in fragment before start of lifecycle?

There is a huge bug I am trying to figure out in my latest project using Kotlin. So how the app works is that it has a Bottom Navigation Activity, and on click on the icons on the Bottom menu, it replaces the container with the respected fragment. On onCreate of the activity, I instantiate all the different fragments with a function defined in a companionObject within the fragments, which returns itself (kind of like a static factory method in java):
override fun onCreate(savedInstanceState: Bundle?) {
{...}
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
fragment1 = Fragment1.newInstance()
fragment2 = Fragment2.newInstance()
}
Then I have this switch to replace the container to the respected fragment on click:
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.f1 -> {
replaceFragment(fragment1)
return#OnNavigationItemSelectedListener true
}
R.id.f2 -> {
replaceFragment(fragment2)
return#OnNavigationItemSelectedListener true
}
}
false
}
{...}
fun replaceFragment(destFragment: Fragment) {
supportFragmentManager
.beginTransaction()
.replace(R.id.container, destFragment)
.addToBackStack(destFragment.toString())
.commit()
}
The fragments are communicating. If you where to click on both fragments, and thereby cause their lifecycles to execute, the code works fine. However, if you do changes in frag1, invoking a communication between the fragments, without having clicked on frag2 in advance, the app crashes. The reason is this line in frag2:
fun saveList(){
val sharedpref : SharedPreferences = context!!.getSharedPreferences("sharedPrefs", MODE_PRIVATE)
{...}
}
Turns out that the context is null, if the user has never invoked the frag2 lifecycle by clicking it. Any ideas to how to fix this problem?
Use Activity context for these case
fun saveList() {
activity?.let {
val sharedpref: SharedPreferences =
it.getSharedPreferences("sharedPrefs", MODE_PRIVATE)
{ }
}
}

Categories

Resources