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()
}
}
Related
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 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.
I'm using navigation components, I have a graph from where I have 3 destinations
FragmentA() -> FragmentB() -> FragmentC()
When I go from FragmentA() to FragmentB() , when pressing the backButton I dont want to come again to FragmentA(), instead I want to pop to FragmentC(), I have set popUpInclusive to true in the action that navigates from FragmentA() to FragmentB() and set popUpTo FragmentC() but when I press backButton on FragmentB() it stills going to FragmentA()
Whats wrong here ?
Solved it by adding a listener into the host activity and pop from there the backstack
override fun onBackPressed() {
when(navController.currentDestination?.id){
R.id.navigation_success -> {
navController.popBackStack(R.id.navigation_success,true)
navController.navigate(R.id.navigation_orders)
}
else -> navController.navigateUp()
}
}
There is a difference between navigateUp and onBackPressed, this two work in different ways, if we use navigateUp we need to touche the navigation graph and from there we can set popupTo and also popupInclusive, but if we are using just the onBackPressed button we need to attach this listener in our main host activity
// This callback will only be called when MyFragment is at least Started.
val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
// Handle the back button event
}
I have a problem about removing a specific Fragment from Backstack.
Like, I have Five Fragments A -> B -> C -> D -> E and all Fragments call from same Activity with add to Backstack and add not use replace.
Calling order; A -> B -> C -> D -> E.
When Fragment-E (last fragment) is my current Fragment and pressed the Back Button then I want to go Fragment-B. That means I don’t require Fragment-C, Fragment-D and Fragment-E.
Also, When Fragment B is there and presses back button then Fragment A will be called. This will also happen with all the fragment simultaneously.
One more thing All Fragment call API to fetch data from the server and fill to list.
Help to achieve this scenario.
You have a method called instanceof for Fragments. So when you are in Fragment E try to check like
while adding do fragmentTransaction.addToBackStack("fragmentC");
if(currentFragment instanceOf FragmentE) {
fragmentManager.popBackStack ("fragmentC", FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
You need to override onBackPressed() in your Activity, From where your added Fragment.
You can create your own logic. like check if current fragment is instanceof FragmentE You need to call getFragmentManager().popBackStack(); three time to remove Fragments C -> D -> E
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
if(getFragmentManager().findFragmentById(R.id.ad_container) instanceof FragmentE) {
getFragmentManager().popBackStack();
getFragmentManager().popBackStack();
getFragmentManager().popBackStack();
// pop fragment D,C from backstack
// navigate to fragment B
}
} else {
super.onBackPressed();
}
}
Hope this will help
In you activity Override onBackPressed() method and add below code
#Override
public void onBackPressed() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
if (fragment instanceof FragmentE) {
getSupportFragmentManager().popBackStack ("fragmentC", FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
} else {
super.onBackPressed();
}
}
Remember don't forget to add .addToBackStack("whichFragment") before transaction.commit(); while adding each fragments.
getSupportFragmentManager().popBackStack ("fragmentC", FragmentManager.POP_BACK_STACK_INCLUSIVE) //this essentially means also remove FragmentC
I am using drawer menu in my app.I select one option from menu and open fragment and from that fragment call an Activity.Since here it is working fine but when I press back button(OnbackPress) then app is crashed.
bellow is the error.
"Unable start activity...ClassCastException...cannot be cast to Home_Tab"
This is MainActivity code.
if (savedInstanceState == null) {
homefragment = Home_tab()
fragmentTransaction = fragmentManager!!.beginTransaction()
fragmentTransaction!!.replace(R.id.frame, homefragment)
fragmentTransaction!!.addToBackStack(null)
fragmentTransaction!!.commit()
} else {
homefragment = supportFragmentManager.fragments[0] as Home_tab //Crash at this line
}
Code from where backPress Called.
override fun onBackPressed() {
super.onBackPressed()
finish()
}
//Add a check like this before casting.
//It is a smart cast and you can directly use the result.
Fragment fragmentZero = supportFragmentManager.fragments[0]
if (fragmentZero is Home_tab) {
//Casting is done, you can directly use fragment here
homefragment = fragmentZero
}
Well based on your code you add the fragment to the fragmentmanager (regular one) but try to get it back from the supportfragmentmanager. Those are two different classes and your fragment can only extend one
You're are mixin fragment manager et support fragment manager, i'll go with suppport one since this is the right way to do it. To get current display fragment added with a container ID use findFragmentById
if (savedInstanceState == null) {
homefragment = Home_tab()
supportFragmentManager?.let{
fragmentTransaction = it.beginTransaction()
fragmentTransaction.replace(R.id.frame, homefragment)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
}
} else {
homefragment = supportFragmentManager.findFragmentById(R.id.frame) as Home_tab
}