I'm having problems coming up with a solution to this problem.
Basically I have a load of tabs in my ActionBar. When each is touched the fragments from the previous tab are detached and the fragments for the new tab are added using replace (if they haven't been instantiated yet) or attached (if they have). I think I got this method from Google and it was working fine until now.
Example of adding a tab's fragments:
if(tab.getText().equals(context.getString(R.string.title_class_tab))) {
if(browser == null) {
browser = CourseBrowserFragment.newInstance(false);
fragmentTransaction.replace(leftContainerId, browser);
} else {
fragmentTransaction.attach(browser);
}
if(lessonViewer == null) {
lessonViewer = LessonViewerFragment.newInstance(false);
fragmentTransaction.replace(rightContainerId, lessonViewer);
} else {
fragmentTransaction.attach(lessonViewer);
}
}
and removing:
if(tab.getText().equals(context.getString(R.string.title_class_tab))) {
if(browser != null) {
fragmentTransaction.detach(browser);
}
if(lessonViewer != null) {
fragmentTransaction.detach(lessonViewer);
}
}
The problem arises from the layout I need for one of the tabs. Basically it's like the Gmail app. There are two fragments (let's say Panel A and Panel B) and when you push a button Panel A slides out, Panel B slides to Panel A's old position and a new, third one (Panel C) slides in from the right.
I had this working fine but now I've added the sliding-in FragmentTransaction to the back stack so that the user can touch the back button and Panel C will slide back out and Panel A will come back. Again, like Gmail.
Except when the user goes to a different tab this transaction is still on the back stack and executes if the user presses back. The fragments end up in crazy places. What I need to do is remove it from the back stack when the user navigates to a different tab. Is there any way I can do this? FragmentManager doesn't seem to let you manually remove things from the back stack and using the popBackStack() method doesn't just remove the transaction, it executes it. I want to remove it when the user navigates away and put it back when the user returns.
I think I can get a hold of the "Back Stack Entry" for this transaction using "getBackStackEntryAt" but it's not much good if I can't remove it and put it back in place when the user comes back to the tab.
The only possible solution I can think of is not using the back stack and overriding onBackButtonPressed instead. From there I could just do a reverse of the transaction if necessary.
Thanks for any help and sorry if I'm being incoherent.
Not sure if this would qualify as a solution but I ended up just not adding the transaction to the back stack and just doing a fresh transaction when the user swiped or pressed back. The transaction just did the reverse of the original one with animations etc.
The way I managed the back button is I set a boolean to true if I was in the layout showing Panel C. If the user swipes back into the Panel A layout or navigates away the boolean is set to false. I then overrode the onBackButtonPressed method in the Activity and if the boolean was true (ie: we're in the Panel C layout) I run that reverse transaction otherwise I just call super.onBackButtonPressed() (ie: perform standard back button behaviour).
Related
I am implementing a login system with Jetpack Compose and I'm using Compose Navigation. I have an "onboarding" screen where the user can choose whether they want to login or sign up. Fromn both the login and sign up screen the user can navigate to the other one, but let's say they keep navigating back and forth, then the back stack is full of those screens.
How should I handle the back stack in this case? I thought about checking if the destination is already present in the back stack and pop to that, but it looks like this has some pitfalls (the user could have visited one of those pages before and pop several destinations for example).
I'd also like to pop the whole login flow when done but I don't get how
How should I handle the back stack in this case? I thought about
checking if the destination is already present in the back stack and
pop to that
In a way you're right, but you don't need to do much of it yourself if you have circular navigation (this is what you describe). You can just use popUpTo from the signup page (if the login is supposed to be the "default" like such:
navController.navigate(loginRoute) {
popUpTo(loginRoute) {
inclusive = true
}
}
The inclusive = true is necessary because you also want to pop the login from the stack itself:
If your stack looks like AB and you navigate back to A, you would pop just B and be left with AA (thus able to navigate back to A again while you're at A).
I'd also like to pop the whole login flow when done but I don't get how
Similar to your initial idea, just pop the entire backstack after you're done using navController.navigate(yourRoute) { popUpTo(0) }
I have written the code below, to try to, first clear everything and then navigate back to the very first screen. When I tap the button, it first goes back to the previous screen then when I go forward and tap again it goes to the Main screen.
rootNavHostController?.navigate("Main"){
popUpTo(rootNavHostController.graph.findStartDestination().id){
inclusive = true
}
}
Why is this strange behavior. And what can I do to resolve it?
EDIT: Even the code below gives the same behavior, it is like, it first in the stack goes one step back and then afterwards navigates to the correct screen. How can I clear everything and navigate directly to the Main screen?
rootNavHostController?.navigate(Graph.ENTRY){
popUpTo(rootNavHostController?.currentBackStackEntry?.destination?.route.toString()){
inclusive = true
}
}
I am absolute beginner to Android. Now I am creating a tutorial project. In my project, there are so many fragments and there is only one activity for that fragment. So when I press the back the button, application always exit. So it does not make any sense for user.
So what I want to do is, I would like to go back to previous fragment if it exists when user press the back button. So I found so many identical question on stackoverflow. All the answers say to do like this. To go back to previous fragment in Activity.
getFragmentManager().popBackStack();
So this is how I override back button listener in my activity:
#Override
public void onBackPressed(){
if ( getFragmentManager().getBackStackEntryCount() > 0)
{
getFragmentManager().popBackStack();
return;
}
}
But it is not working when I press the back button. Is this correct way to do it?
When you replace / add fragment, did you addToBackStack?
Demo :
getFragmentManager().beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE).replace(R.id.app_bar_main_container, YOUR_FRAGMENT,YOUR_FRAGMENT_TAG).addToBackStack(YOUR_FRAGMENT_TAG).commit();
YOUR_FRAGMENT_TAG needs to be unique for each fragment to be able to go back to all previous fragments.
I have a DialogFragment, call it A, which presents an option that leads to a second DialogFragment, B, being displayed. B provides further options.
The functionality I require is as follows:
Making a selection in A leads to B being displayed (as stated above).
If the user hits back while B is being displayed, A should be resumed into view.
If the user makes a selection in B, then B should dismiss and A should not reappear.
In A, inside an onItemClick() handler I cause B to appear using:
FragmentManager manager = getActivity().getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.remove(DialogFragmentA.this);
transaction.addToBackStack("transaction_label");
DialogFragmentB dialogFragment = DialogFragmentB.newInstance( ...some args here...);
dialogFragment.show(transaction, "frag_B");
I call .addToBackStack() as I understand this will cause the back key to pop and reverse the transaction. That is, replace B with A again.
So far, requirements 1 and 2 are met.
B makes use of AlertDialog.Builder. A positive button is used with listener. When that positive button is pressed, I want requirement 3 to be met. That is, B should dismiss and A should not reappear. But what actually happens is A appears again.
I am assuming here that within the implementation of AlertDialog's positive button is a call to dismiss() which causes the back stack to be popped, resulting in A appearing again. Is this the case?
What I have tried to do is, within the positive button's onClick(), is to obtain the FragmentManager and call .popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE). But this has no apparent effect; A continues to appear. Using popBackStackImmediate() has no effect either.
Is this perhaps because the event loop has already somehow committed to popping the back stack by the time the positive button listener's onClick() executes?
I'd be grateful for an explanation for what is occurring and how I can make it work as intended.
I think I have nailed it, helped by the question and answer here.
The key point is that the sequence of transactions is nothing -> A -> B. I want to pop the back stack to the point where I have nothing. Therefore, as well as calling .addToBackStack() for the A -> B transaction, I also need to call .addToBackStack() for the nothing -> A transaction. Then, all of those transactions can be popped off the back stack again to get back to the point of nothing: The first pop does B -> A, then the next pop does A -> nothing.
For both transactions I am now calling .addToBackStack("some_transaction"). In B's positive onClick(), I call manager.popBackStack("some_transaction", FragmentManager.POP_BACK_STACK_INCLUSIVE);. I might rethink the tags I'm giving to the transactions, but this is now working as I want it to.
So the basic point seems to be that, if you want it to be possible to go back to a DialogFragment but also go back to the point where that DialogFragment wasn't shown at all, you have to add the transaction that put it there in the first place onto the back stack, so you can pop back to that point.
I previously thought it would be possible to achieve what I wanted by only adding the A -> B transaction to the back stack, and then somehow completely purging (rather than popping) the back stack to prevent A appearing again once a selection had been made in B. But it seems that there is no way to actually 'purge' the back stack; you can only pop and reverse the transactions.
I'm currently working on a project that has an activity which is consisted of two fragments.
The first fragment shows a custom expandable list. Every row is created from a custom layout that has a checkbox in the right side of it.
The second fragment shows more details about the clicked row from the list. In order to open the second fragment, the user has to click on the row. The checkbox is used for another reason.
So, what I'm trying to do is to display these two fragments side by side only when the application runs in tablets. When the app runs in handsets and the user presses one row, the second fragment should be displayed on top.
Furthermore, I have an action bar at the top of the screen which has implemented the usual back button.
The problem exists when I open the second fragment when I have already selected some checkboxes. When I press the back button, which navigates me to the first fragment, the checkboxes will not be checked.
The onSaveInstanceSate method is obviously not called (as the parent activity is not getting paused), so I can't save the ArrayList that stores the checked rows.
Last but not least, the fragments are being added dynamically.
The question
How can I properly implement the back button so when the user uses a
tablet, the back button should be used in order to close the activity, or a
handset, so the back button should be used as a navigation back to the first fragment with the ability to restore it's previous state?
if (mFragmentManager.getBackStackEntryCount() == 0) {
LogUtil.d(TAG,
"home fragment" + mFragmentManager.getBackStackEntryCount());
this.finish();
} else {
mFragmentManager.popBackStackImmediate();
}
try this should work , happy coding
My first idea would be to create a boolean in the resources of your project: in the "values" directory, your boolean would be false, for example, and in your "values-sw600dp" and "values-sw720dp-land" directory, the boolean would be true.
Then, in your code, you would check the boolean (using R.boolean.your_boolean) to know if this is a tablet or a handset.
Then, with a simple if/else, you would implement your code, depending on the value of your boolean...
if(yourBoolean){
//We are on a tablet
finish();
}else{
//We are on a handset
//Your code to navigate back...
}
You need to (1) detect if the user is on a tablet and (2) control the back function accordingly. I'm not sure how you're currently detecting whether the device is a tablet but a very easy method is described here. It involves a boolean resource that you can access when customizing your back function to determine the device type.
What I would do is override onBackPressed in your hosting Activity and control back function from there
#Override
public void onBackPressed(){
boolean tabletSize = getResources().getBoolean(R.bool.isTablet);
if (tabletSize){
moveTaskToBack(true);
} else {
//handle fragment back stack
}
}
The info on handling the back stack and replacing the fragment is here in the android docs. I will update that section later but I have to run for now.