I have code below that adds random fragments(First,SecondFragment or ThirdFragment)
private fun addFragment() {
activeFragment = if (activeFragment is FirstFragment) {
SecondFragment()
} else if (activeFragment is SecondFragment) {
ThirdFragment()
} else {
FirstFragment()
}
fragmentTransaction = fragmentManager!!.beginTransaction()
fragmentTransaction?.add(R.id.fragment_container, activeFragment!!)
?.addToBackStack(null)
?.commit()
}
override fun onBackPressed() {
val fragment = fragmentManager?.findFragmentById(R.id.fragment_container)
if (fragment != null) {
Log.d("mytag", "onBackPressed: $fragment")
fragmentManager?.popBackStack()
return
}
super.onBackPressed()
}
In the code above everything works fine. I mean when i press back button it pops the last fragment and shows previous if there any.
But if i remove return statement inside if check (in onBackPressed function), then it pops 2 fragments at once from back stack.(I added onBackStackChangedListener and i can see how many items are left when i pop, so if there are 3 fragments i back stack when i pop it removes 2 fragments and only 1 remains, and then if i click back again, it removes last fragment and also destroys the app)
The reason you're seeing this behavior is because Fragments already handle the system back button when you use addToBackStack() as part of the super.onBackPressed() call.
That means you don't need to override onBackPressed() at all when using fragments.
Related
I have anAndroid navigation drawer with a menu that tapping on items open fragments. Good so far.
Some of those fragments need to open a new one for more details. I do that using this code from the first fragment:
getParentFragmentManager().beginTransaction().replace(R.id.nav_host_fragment_content_main, new MySecondFragment(), null).commit();
Tappin on the backbutton (the backarrow on the top-left corner) doesn't take me back to MyFirstFragment. It throughs an exceptions:
java.lang.IllegalStateException: Fragment MyFirstFragment not
associated with a fragment manager.
I can use this other code:
getParentFragmentManager().beginTransaction().add(R.id.nav_host_fragment_content_main, new MySecondFragment(), null).commit();
But with this code both fragments are visible at the same time. I mean, MySecondFragment views are displayed but I see in the background MyFirstFragment views.
Any idea on how I can open MySecondFragment from MyFirstFragment , and then, if the back arrow is pressed I want to return to the same place MyFirstFragment was before.
Thanks
NEW ANSWER:
Using the navigation drawer, it seems the fragment transactions happen under the NavHostFragment and its FragmentTransactionManager.
This forces us to get its childFragmentManager() and use it for checking the backStackEntryCount.
Therefore just adding the nested fragment to the parent backstack is not enough. We would need to override onBackPressed and onSupportNavigateUp and take into account the backstack of the NavHostFragment when going back and poping it.
private fun handleNestedFragmentsBackStack(): Boolean {
val navHostChildFragmentManager = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment_content_main)?.childFragmentManager
return if (navHostChildFragmentManager?.backStackEntryCount!! > 1) {
navHostChildFragmentManager.popBackStack()
false
} else {
val navController = findNavController(R.id.nav_host_fragment_content_main)
navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}
override fun onBackPressed() {
handleNestedFragmentsBackStack()
}
override fun onSupportNavigateUp(): Boolean {
return handleNestedFragmentsBackStack()
}
Now when adding your second fragment to the parent's backstack, remember that the parent isn't the Activity, but the NavHostFragment.
You can proceed by doing the fragment transaction as suggested bellow in the initial answer.
INITIAL ANSWER:
Should work for standard fragment transactions
Adding your fragment to the backstack should solve your issue
FragmentTransaction ft = getParentFragmentManager().beginTransaction()
ft.replace(R.id.nav_host_fragment_content_main, new MySecondFragment(), null)
ft.addToBackStack(MySecondFragment.class.getName()) // you can use a string here, using the class name is just convenient
ft.commit();
When pressing the back button, you will navigate through the back stack.
The same in Kotlin code:
parentFragmentManager.commit {
replace(R.id.nav_host_fragment_content_main, MySecondFragment())
addToBackStack(MySecondFragment::class.java.name)
}
Filip Petrovski solution In Java code:
In the AppCompatActivity with the Navigation Drawer:
#Override
public boolean onSupportNavigateUp() {
FragmentManager oChildFragmentManager = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_content_main).getChildFragmentManager();
if(oChildFragmentManager.getBackStackEntryCount() > 1){
oChildFragmentManager.popBackStack();
return true;
}
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
return NavigationUI.navigateUp(navController, mAppBarConfiguration) || super.onSupportNavigateUp();
}
#Override
public void onBackPressed() {
FragmentManager oChildFragmentManager = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_content_main).getChildFragmentManager();
if(oChildFragmentManager.getBackStackEntryCount() > 1){
oChildFragmentManager.popBackStack();
return;
}
super.onBackPressed();
}
Any time you open a fragment do not forget addToBackStack:
getParentFragmentManager().beginTransaction().replace(R.id.nav_host_fragment_content_main, new MyNewFragment()).addToBackStack("").commit();
But with this code both fragments are visible at the same time.
You can set background color to its root layout.xml
Also you can use fragmentTransaction.addToBackStack(null) to add this transaction to the back stack
I have two fragments in my Kotlin code.
When I'm pressing some of the buttons then the First fragment will inflate the second fragment.
The second fragment is displayed and all works fine but when I'm pressing the back button then the Phone is going to the Home page (The application is minimized), when I click on the Recently viewed apps that open all the Opened applications on the screen and choosing my Application (that is opened) then the application got back to Fragment Alike its suppose to be.
But I don't understand why the application is minimized when I'm clicking on the back button?
I just want it to go back to fragment A and do not minimize the application.
This is the code to inflate the second fragment:
val fragment2 = details_frag()
val fragmentManager: FragmentManager? = fragmentManager
val fragmentTransaction: FragmentTransaction =
fragmentManager!!.beginTransaction()
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
fragmentTransaction.apply {
replace(R.id.fragSec, fragment2)
commit()
}
} else {
fragmentTransaction.apply {
replace(R.id.flFragment, fragment2)
commit()
}
}
The Code in the Main Activity that inflate the first fragment is:
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
supportFragmentManager.beginTransaction().apply {
replace(R.id.fragLand, firstFrag)
commit()
}
supportFragmentManager.beginTransaction()
.add(
R.id.fragSec,
details_frag::class.java,
null
) // .addToBackStack(null)
.commit()
} else {
supportFragmentManager.beginTransaction().apply {
replace(R.id.flFragment, firstFrag)
commit()
}
}
I don't see something unusual here, it all works just great but it's just minimize my app when I'm going from the second fragment to the first fragment...
(The first is inserted inside the Main Activity like you can see and I just swap the first fragment with the second one when someone clicks on something in my code...)
Thank you!!!
You need to call addToBackstack in the FragmentTransaction (like you're doing in the second example). That makes the current transaction you're performing (replacing fragment A with B) a new state on the stack. Then when the user hits back, that state can be popped off and the transaction reversed, so you're back with fragment A in place.
If you don't add the transaction to the backstack, it becomes part of the most recent state, which is when you added A - that state becomes "added A and then replaced it with B", and when you pop that off, it goes back to "before you added A"
I want to clear selection on back button pressed on selection tracker when there are selected items and use default back button functionallity if there are no selected items.
I use Navigation Components for working with fragments.
Code in my fragment which implements OnBackPressedListener interface:
override fun onBackPressed() {
if (tracker?.hasSelection() == true) tracker?.clearSelection()
else findNavController().popBackStack()
}
Clear selection condition is working, but else block does not.
Code from my BaseActivity which determines how to use back button.
override fun onBackPressed() {
val currentFragment = navHost.childFragmentManager.fragments[0]
if (currentFragment is OnBackPressedListener) {
currentFragment.onBackPressed()
} else {
super.onBackPressed()
}
}
So, question is how to exit fragment (or pop backstack) when there are no selected items in selection tracker? It is working when I call
requireActivity().finish()
, but I do not know if this is correct solution.
For this case, you could try registering OnBackPressedCallback on your Fragments via addOnBackPressedCallback. For more detail follow the below-given link:
https://developer.android.com/guide/navigation/navigation-custom-back
I have here a sample GitHub project. It's just an activity inflating other fragments in it, by tapping bottom tabs:
Applaunch inflates Fragment 1
Pressing 1.Tab -> inflates Fragment 1
Pressing 2.Tab -> inflates Fragment 2
Pressing 3.Tab -> inflates Fragment 3
and all used by means of this.supportFragmentManager?.beginTransaction()?.replace(). For me it acts like a FragmentTransaction.add(), because:
When I start the app (Fragment 1 is loaded) and press
2nd Tab for Fragment 2 (Toast Frag 2)
and 3rd Tab for Fragment 3 (Toast Frag 3)
then backbutton (Toast Frag 2)
again backbutton (Toast Frag 1)
All the stack is working back. So nothing was "replaced", everything was added?
Whenever I press back, I would like to load "Fragment 1", which is the initial Fragment for the activity. How?
I guess it is happening because the fragments are being replaced but they are also being added to the BackStack as follows:
BaseActivity.kt
// Note the .addToBackStack(null)
this.supportFragmentManager?.beginTransaction()?.replace(resId, newFragment)?.addToBackStack(null)?.commit()
From Documentation for addToBackStack
Add this transaction to the back stack. This means that the transaction will be remembered after it is committed, and will reverse its operation when later popped off the stack.
You may want to check this question to check more info about the difference between add, replace and addToBackStack.
If you don't wanna to "remember and restore" the fragment transition, just remove the call to addToBackStack.
Edit
If you always want to return to first fragment, you can do:
Keep the addToBackStack(). So, the navigation history will be retained
this.supportFragmentManager?.beginTransaction()?.replace(resId,
newFragment)?.addToBackStack(null)?.commit()
Override onBackPressed on MainActivity or BaseActivity and request to always return to first transaction commit:
override fun onBackPressed() {
if(this.supportFragmentManager?.getBackStackEntryCount() > 0) {
this.supportFragmentManager?.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
} else {
super.onBackPressed()
}
}
Just note this is a test code and I could not verify. Also, I'm not familiar with Kotlin. So, this was the best code I could come with... Test and let me know the result... For any case, you can get the ideia.
Another option:
Remove the addToBackStack():
this.supportFragmentManager?.beginTransaction()?.replace(resId, newFragment)?.addToBackStack(null)?.commit()
Then, you must manually control which fragment should be displayed when user presses the back key... something like:
private var int : mCurrentFragment = 1
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
mCurrentFragment = 1
...
}
R.id.navigation_dashboard -> {
mCurrentFragment = 2
...
}
R.id.navigation_notifications -> {
mCurrentFragment = 3
...
}
}
false
}
override fun onBackPressed() {
if(mCurrentFragment != 1) {
replaceFragment(R.id.container_main_layout, Fragment1())
} else {
super.onBackPressed()
}
}
I'm studying menu and toolbar/actionbar. I'm trying the way: one activity container and many fragments. I created and setup a toolbar as actionbar for MainActivity (onCreate):
val myToolbar = this.findViewById<Toolbar>(R.id.myToolbar)
setSupportActionBar(myToolbar)
Then, I add itens by normal way with onCreateOptionMenu and handle click with onOptionsItemSelected
When a call Fragment 1, I change action bar and add back button like this (onCreate):
val actBar = (activity as AppCompatActivity).supportActionBar
actBar?.setDisplayHomeAsUpEnabled(true)
actBar?.setDisplayShowHomeEnabled(true)
actBar?.setDisplayUseLogoEnabled(false)
actBar?.title = "Fragment 1 toolbar"
actBar?.subtitle = ""
setHasOptionsMenu(true)
Then from Fragment 1, the Fragment 2 is called and setup as same way:
To handle back button click in fragments, in onOptionsItemSelected:
return if (item.itemId == android.R.id.home) {
activity?.onBackPressed()
true
} else return when (item?.itemId){
...
}
else -> super.onOptionsItemSelected(item)
}
And override onBackPressedin MainActivity:
override fun onBackPressed() {
if(supportFragmentManager.backStackEntryCount > 0){
supportFragmentManager.popBackStackImmediate()
}else{
super.onBackPressed()
}
}
The problem is: if I click on back button, it's backing as expected but the last object of action bar is showed. In MainActivity, only action itens are showed as expected:
How I can sync the bar according fragment and activity?
Note:
I'm using Kotlin, but Java solution are welcome (to convert to kotlin
later)
The fragments are added to back stack
I found a solution. I leave here for whoever interests:
I applied OnBackStackChangedListener that watch changes on back stack. Then, You can make any changes on UI.
supportFragmentManager.addOnBackStackChangedListener {
//UI changes
}
Inside, I check if has some fragment current using the fragment container:
supportFragmentManager.addOnBackStackChangedListener {
val currentFragment = supportFragmentManager.findFragmentById(R.id.you_fragment_container)
if (currentFragment == null){
//rebuild action bar here or make any another changes
}
}
In my case, I compare null that mean container has no fragment. So, if null, the root activity is on screen.
This can be used to make changes for any fragment you want to.
That's it.