Back From fragment B to Fragment A not working Kotlin - android

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"

Related

Why Fragmenttransaction replace() acts like a add()?

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()
}
}

Conditional Navigation in Android Navigation Component

I have 3 fragments A, B and C with A being the start destination of my nav graph. When the user starts the app, I check in fragment A if there are previously stored results. If there are, I want to navigate straight to fragment C. I have managed to get this to work. However, when the user presses back in fragment C in this case, I want them to be taken to fragment B instead of A, and that's what I need help figuring out.
Note: Fragment A is just a setup fragment which is only visited once when the app starts. Which means when the user presses back from fragment B, they are taken to OS home screen.
You can override onBackPress() and replace whatever fragment you are in with FragmentB like this
#Override
public void onBackPressed() {
int stackCount = getFragmentManager().getBackStackEntryCount();
if (stackCount == 1) {
super.onBackPressed(); // if you don't have any fragments in your backstack yet.
} else {
// just replace container with fragment as you normally do;
FragmentManager fm = getFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);//clear backstackfirst and then you can exit the app onbackpressed from home fr
FragmentTransaction transaction = fm.beginTransaction();
transaction.replace(R.id.container, new FragmentB());
transaction.commit();
}
}
This is the original answer ,you can take a look answer by Rainmaker
You can override your onBackPressed on fragment C and call your navController to go back to fragment b creating a new action from C to B
navController.navigate(R.id.action_CFragment_to_BFragment)

How to replace top fragment?

I have an application with 4 fragments: MainFragment, ActionFragment, DoneFragment, FailedFragment. When application launched it shows MainFragment. Than application receive some event and show ActionFragment with two buttons 'yes' and 'no'. If user press 'yes', applicaiton shows DoneFragment, otherwise FailedFragment. When user press one time to back button on ActionFragment, DoneFragment or FailedFragment application must show MainFragment.
Improtant: if ActionFragment, DoneFragment or FailedFragment already opened and some event is occure again, application should show ActionFragment fragment with new event data.
So, I need:
if ActionFragment, DoneFragment or FailedFragment already opened and event occur, I should replace top fragment with ActionFragment
Otherwise I should simply add ActionFragment.
I am trying:
fun addOrReplaceFragment(fragment: Fragment, tag: String) {
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val previous = fragmentManager.findFragmentByTag(tag)
if (previous == null) {
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
fragmentTransaction.add(R.id.main_fragment_container, fragment, tag)
fragmentTransaction.addToBackStack(tag)
} else {
fragmentManager.popBackStack(previous.id, 0)
fragmentTransaction.remove(previous)
fragmentTransaction.add(R.id.main_fragment_container, fragment, tag)
fragmentTransaction.addToBackStack(tag)
}
fragmentTransaction.commitAllowingStateLoss()
}
// ...
addOrReplaceFragment(ActionFragment(), "singleTag")
// ...
addOrReplaceFragment(DoneFragment(), "singleTag")
// ...
addOrReplaceFragment(FailedFragment(), "singleTag")
Here is popBackStack() doesn't work. When ActionFragment is opened, DoneFragment or FailedFragment just adding above. And user have to press back two times to get back to MainFragment.
I am find solution change popBackStack() to popBackStackImmediate(). It works well, but if activity is minimized it produce crash with IllegalStateException, because popBackStackImmediate() cannot be called after onSaveInstanceState().
How to replace top fragment and avoid IllegalStateException?
Try this :
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.FragmentToBeReplaced,theFragmentToBeAdded);
ft.commit();
https://developer.android.com/reference/android/app/FragmentManager.html#isStateSaved()
Use isStateSaved to avoid losing state when a transaction happened.
And I think Android navigation component might be easier way to navigate between fragments.

Android fragment simple backstack

I have one activity containing one container that will receive 2 fragments.
Once the activity initialises i start the first fragment with:
showFragment(new FragmentA());
private void showFragment(Fragment fragment) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, fragment, fragment.getTag())
.addToBackStack(fragment.getTag())
.commit();
}
Then when the user clicks on FragmentA, I receive this click on Activity level and I call:
showFragment(new FragmentB());
Now when I press back button it returns to fragment A (thats correct) and when i press again back button it show empty screen (but stays in the same activity). I would like it to just close the app (since the activity has no parent).
There are a lot of posts related with Fragments and backstack, but i can't find a simple solution for this. I would like to avoid the case where I have to check if im doing back press on Fragment A or Fragment B, since i might extend the number of Fragments and I will need to maintain that method.
You are getting blank screen because when you add first fragment you are calling addToBackStack() due to which you are adding a blank screen unknowingly!
now what you can do is call the following method in onBackPressed() and your problem will be solved
public void moveBack()
{
//FM=fragment manager
if (FM != null && FM.getBackStackEntryCount() > 1)
{
FM.popBackStack();
}else {
finish();
}
}
DO NOT CALL SUPER IN ONBACKPRESSED();
just call the above method!
addToBackStack(fragment.getTag())
use it for fragment B only, not for fragment A
I think your fragment A is not popped out correctly, try to use add fragment rather replace to have proper back navigation, however You can check count of back stack using:
FragmentManager fragmentManager = getSupportFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
and you can also directly call finish() in activity onBackPressed() when you want your activity to close on certain fragment count.

How to correctly handle multiple fragment change in Android

Assume I have 4 fragments A B C and D.
A and B are major fragments, C and D are minor.
I use navigation drawer to switch fragments.
A is the default starting fragment.
I want to achieve following features but cannot figure out how to play with the fragment manager and transactions.
A -> B or B -> A, replace current fragment, do not push backstack, but I want to keep the current fragment status (e.g. list view position) after navigate back
A/B -> C/D, add C/D on top of A/B, using back button to navigate back to A/B.
C -> D or D -> C, replace current fagment
C/D -> A/B, remove current fragment C/D and show A/B
Is the only way to implement this function that I should write some complicated function for switching the fragments (and also need to keep what is current fragment and what is the wanted target fragment)?
Is there better way out?
According to #DeeV 's answer, I came out with something like following.
LocalBrowse and WebsiteExplore are main fragments while Settings and About are sub fragments.
It seems to work fine but still a little bit ugly, any better idea?
private void switchToFragment(Class<?> targetFragmentClz) {
if(mCurrentFagment!=null && mCurrentFagment.getClass().equals(targetFragmentClz)) {
return;
}
BaseFragment targetFragment = null;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if(targetFragmentClz.equals(LocalBrowseFragment.class)
|| targetFragmentClz.equals(WebsiteExploreFragment.class)) {
if(mCurrentFagment instanceof SettingsFragment //mCurrentFragment will not be null this time
|| mCurrentFagment instanceof AboutFragment) {
transaction.remove(mCurrentFagment);
}
if(mCurrentMainFagment==null || !mCurrentMainFagment.getClass().equals(targetFragmentClz)) {
targetFragment = (BaseFragment) Fragment.instantiate(this, targetFragmentClz.getName());
targetFragment.setHasOptionsMenu(true);
transaction.replace(R.id.ac_content_frame_main, targetFragment);
mCurrentMainFagment = targetFragment;
}
} else {
targetFragment = (BaseFragment) Fragment.instantiate(this, targetFragmentClz.getName());
targetFragment.setHasOptionsMenu(true);
getSupportFragmentManager().popBackStackImmediate();
transaction.replace(R.id.ac_content_frame_sub, targetFragment)
.addToBackStack(null);
}
transaction.commit();
mCurrentFagment = targetFragment;
}
One method that I can think of is to stack the two types of fragments on each other. So a system like this:
<FrameLayout>
<FrameLayout id="main_container">
<FrameLayout id="sub_container">
<FrameLayout>
Would mean that you have two containers holding fragments. The top one completely covers the other. Thus, you could have two method likes this:
public void swapMainContainer(FragmentManager fm, Fragment frag)
{
fm.beginTransaction().
.replace(R.id.main_container, frag, "TAG")
.commit();
}
public void swapSubContainer(FragmentManager fm, Fragment frag)
{
fm.popBackstackImmediate();
fm.beginTransaction()
.replace(R.id.sub_container, frag, "SUBTAG")
.addToBackStack(null)
.commit();
}
So if you use swapMainContainer() only with Fragment A and Fragment B, they will constantly replace each other but the commits won't be added to the backstack.
If you use swapSubContainer() only with Fragment C and Fragment D, they will likewise replace each other, but "Back" will close them. You are also popping the backstack every time you commit a sub Fragment thus removing the previous commit. Though, if there's nothing in the backstack, it won't do anything.
To remove C/D, simply call popBackStack() and it will remove them from the stack.
The flaw in this approach however is if you have more than these two Fragments that are added to the backstack. It may get corrupted.
EDIT:
Regarding saving view state, the fragment itself will have to handle that via this method.

Categories

Resources