I have a menu with 4 menu options... Each of these options shows the corresponding fragment.
I have 3 functions to add,show and hide a fragment:
private void addFragment(Fragment newFragment, String fragmentName) {
fragmentManager
.beginTransaction()
.add(R.id.content_frame, newFragment,
fragmentName).addToBackStack(null)
.commit();
}
private void hideFragment(Fragment existingFragment) {
if(existingFragment!=null && existingFragment.isVisible()){
fragmentManager.beginTransaction().hide(existingFragment).addToBackStack(null).commit();
}
}
private void showFragment(Fragment existingFragment) {
if(existingFragment!=null){
fragmentManager.beginTransaction().show(existingFragment)
.addToBackStack(null).commit();
}
}
The navitemswitchlogic is as follows:
onNavItemSelected(int id) {
switch (id) {
case 1: //option 1 - hide rest of the fragments and show Option1Fragment
hideFragment(option2Fragment);
hideFragment(option3Fragment);
hideFragment(option4Fragment);
if (Option1FragmentDoesnotExist) { //create option1Fragment and add it to FragmentTransaction
option1Fragment = new option1Fragment();
addFragment(option1Fragment,"option1Fragment");
} else
showFragment(option1Fragment);
break;
case 2:
//same as option 1
}
}
What I expect to happen:
Whats happening now:
I know I am making some mistake adding and retrieving fragments to/from the backstack..But,I am not sure what exactly it is. I tried doing all these as a part of the same transaction as well.. That dint work too.. Also, I dont want to replace the fragments... Hope I framed my question right..
Any help is appreciated.Thanks!
It's a bit late to answer this question now, but for those who now come across the similar issue, here is the explanation.
Each back press will reverse a single fragment transaction. So basically what you have in the example above is that you have 2 transactions on navigation click.
1st one is Hiding fragment and second is showing a new one. Once you press back, it will reverse showing new one but nobody will reveal the hidden one and you are left with the Blank/empty screen.
On the next back press you will reverse hide action, so fragment will now appear.
What you need to do is to execute both hiding and showing in a single transaction. In that way, a single back press will know that you want to reverse adding and hiding a fragment below.
fragmentManager.beginTransaction().hide(existingFragment).add(newFragment).addToBackStack(null).commit();
Related
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.
I want to create 3 tabs and each one have single fragment,when i press back button in device instead of closing of app i need to go previous tab.I had try a lot but not find correct answer which is suitable for my app.I am new to development.
What i have in my project is 3 tabs when i am at tab 3 if i press it needs to go tab 2 instead of closing app.
What ever suggestions i get those are helped only go back to previous fragment.
But i want go to previous tab when we press back button in android device.
This can be easily achieved by overriding onBackPressed().
The key here is to keep track of your current fragment and swatch tabs based on that info.
#Override
public void onBackPressed() {
switch(activeFragment){
case 3: triggerTab2();
break;
case 2: triggerTab1();
break;
case 1: super.onBackPressed();
break;
}
}
If you have different fragments in one tab if you press back button by using OnBackPressed() we can go for previous fragment.For that situation following code will help you.
#Override
public void On BackPressed(){
//You can check by write toast if it working are not.
FragmentManager manager = getSupportFragmentManager();
if (manager.getBackStackEntryCount() > 0) {
// If there are back-stack entries, leave the FragmentActivity
// implementation take care of them.
//implement your code here.
super.onBackPressed();
}
}
It will help you to go back of your previous fragments not previous tabs.
this will help you understand.
Separate Back Stack for each tab in Android using Fragments
For creating Tabs with fragments Check this link -
http://www.androidhive.info/2015/09/android-material-design-working-with-tabs/
For on Back button pressed you need to override onBackPressed() in the activity not in fragments. You need to handle back pressed and provide index of respective tab.
Hope it will help
Use this
#Override
public void onBackPressed() {
// put switch or if which decide which fragment
Class fragmentClass = Fragment.class;
Fragment fragment = null;
try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (java.lang.InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
((Activity) getContext()).getSupportFragmentManager().beginTransaction()
.replace(R.id.content_fragment, fragment).commit();
// R.id.content_fragment => id of viewPager
}
So basically what I'm working on is very similar to Instagram application, where there're a number of tabs and users can switch to any tab without any delay no matter what there's anything going on, such as refreshing, reloading, and etc. It also uses back button to go back to the previous preserved tab.
In order to achieve this, I've used FragmentManager with FragmentTransaction to show and hide each fragment which represents each tab. I didn't use replace or attach / detach because they destroy view hierarchy of previous tab.
My implementation works pretty well except that showing and hiding fragments are not committed (I highly doubt that this is a right word to say but so far that's how I understood the flow.), or don't occur immediately when SwipeRefreshLayout is refreshing on the fragment (to be hidden) which was added to FragmentManager later than the one to show.
My implementation follows the rules like these. Let's say we have 4 tabs and my MainActivity is showing the first tab, say FirstFragment, and the user selects the second tab, SecondFragment. Because SecondFragment had never been added before, I add it to FragmentManager by using FragmentTransaction.add and hide FirstFragment by using FragmentTransaction.hide. If the user selects the first tab again, because FirstFragment was previously added to FragmentManager, it doesn't add but only show FirstFragment and just hide SecondFragment. And selecting between these two tabs works smoothly.
But when the user "refreshes" SecondFragment's SwipeRefreshLayout and selects the first tab, FragmentTransaction waits for SecondFragment's refresh to be finished and commits(?) the actual transaction. The strange thing is that the transaction is committed immediately the other way around, from FirstFragment's refresh to SecondFragment.
Because this occurs by the order of addition to FragmentManager, I doubt that the order of addition somehow affects backstack of fragments and there might exists something like UI thread priority so that it forces the fragment transaction to be taken place after later-added fragment's UI transition finishes. But I just don't have enough clues to solve the issue. I've tried attach / detach and backstack thing on FragmentTransaction but couldn't solve the issue. I've tried both FragmentTransaction.commit and FragmentTransaction.commitAllowingStateLoss but neither solved the issue.
These are my MainActivity's sample code.
private ArrayList<Integer> mFragmentsStack; // This is simple psuedo-stack which only stores
// the order of fragments stack to collaborate
// when back button is pressed.
private ArrayList<Fragment> mFragmentsList;
#Override
protected void onCreate() {
mFragmentsStack = new ArrayList<>();
mFragmentsList = new ArrayList<>();
mFragmentsList.add(FirstFragment.newInstance());
mFragmentsList.add(SecondFragment.newInstance());
mFragmentsList.add(ThirdFragment.newInstance());
mFragmentsList.add(FourthFragment.newInstance());
mMainTab = (MainTab) findViewById(R.id.main_tab);
mMainTab.setOnMainTabClickListener(this);
int currentTab = DEFAULT_TAB;
mFragmentsStack.add(currentTab);
getSupportFragmentManager().beginTransaction().add(R.id.main_frame_layout,
mFragmentsList.get(currentTab), String.valueOf(currentTab)).commit();
mMainTab.setCurrentTab(currentTab);
}
// This is custom interface.
#Override
public void onTabClick(int oldPosition, int newPosition) {
if (oldPosition != newPosition) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
// First hide the old tab.
fragmentTransaction.hide(mFragmentsList.get(oldPosition));
// Recalculate the fragment stack.
if (mFragmentsStack.contains(newPosition)) {
mFragmentsStack.remove((Integer) newPosition);
}
mFragmentsStack.add(newPosition);
// Add new fragment if it's not added before, or show new fragment which was already hidden.
Fragment fragment = getSupportFragmentManager().findFragmentByTag(String.valueOf(newPosition));
if (fragment != null) {
fragmentTransaction.show(fragment);
} else {
fragmentTransaction.add(R.id.main_frame_layout, mFragmentsList.get(newPosition),
String.valueOf(newPosition));
}
// Commit the transaction.
fragmentTransaction.commitAllowingStateLoss();
}
}
// It mimics the tab behavior of Instagram Android application.
#Override
public void onBackPressed() {
// If there's only one fragment on stack, super.onBackPressed.
// If it's not, then hide the current fragment and show the previous fragment.
int lastIndexOfFragmentsStack = mFragmentsStack.size() - 1;
if (lastIndexOfFragmentsStack - 1 >= 0) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.hide(mFragmentsList.get(mFragmentsStack.get(lastIndexOfFragmentsStack)));
fragmentTransaction.show(mFragmentsList.get(mFragmentsStack.get(lastIndexOfFragmentsStack - 1)));
fragmentTransaction.commitAllowingStateLoss();
mMainTab.setCurrentTab(mFragmentsStack.get(lastIndexOfFragmentsStack - 1));
mFragmentsStack.remove(lastIndexOfFragmentsStack);
} else {
super.onBackPressed();
}
}
Just faced the same issue with only difference - I'm switching fragments on toolbar buttons click.
I've managed to get rid of overlapping fragments overriding onHiddenChanged:
#Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
yourSwipeRefreshLayout.setRefreshing(false);
}
}
I have an application running a single activity with multiple (2) fragments at a given time. I've got a fragment on the left which functions as a menu for which fragment to
display on the right hand side.
As an example lets say the menu consists of different sports; Football, Basketball, Baseball, Skiing, etc. When the user selects a sport, a fragment with details on the specific sport is displayed to the right.
I've set up my app to display two fragments at once in layout-large and layout-small-landscape. In layout-small-portrait however, only one fragment is displayed at a given time.
Imagine this; a user is browsing the app in layout-small-landscape (two fragments at a time) and selects a sport, Football. Shortly after he selects Basketball. If he now chooses to rotate into layout-small-portrait (one fragment at a time) I want the following to happen:
The Basketball fragment should be visible, but if he presses the back button, he should return to the menu and not to the Football fragment (!) which by default is the previous fragment in the back stack.
I have currently solved this like the following:
....
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// static fragments
if(menuFragment == null) menuFragment = new MenuFragment();
if(baseFragment == null) baseFragment = new TimerFragment(); // default content fragment
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
// Determine what layout we're in..
if(app().getLayoutBehavior(this) == LayoutBehavior.SINGLE_FRAGMENT) {
// We are currently in single fragment mode
if(savedInstanceState != null) {
if(!rotateFromSingleToDual) {
// We just changed orientation from dual fragments to single fragment!
// Clear the entire fragment back stack
for(int i=0;i<getSupportFragmentManager().getBackStackEntryCount();i++) {
getSupportFragmentManager().popBackStack();
}
ft.replace(R.id.fragmentOne, menuFragment); // Add menu fragment at the bottom of the stack
ft.replace(R.id.fragmentOne, baseFragment);
ft.addToBackStack(null);
ft.commit();
}
rotateFromSingleToDual = true;
return;
}
rotateFromSingleToDual = true;
ft.replace(R.id.fragmentOne, menuFragment);
} else if(app().getLayoutBehavior(this) == LayoutBehavior.DUAL_FRAGMENTS) {
// We are now in dual fragments mode
if(savedInstanceState != null) {
if(rotateFromSingleToDual) {
// We just changed orientation from single fragment to dual fragments!
ft.replace(R.id.fragmentOne, menuFragment);
ft.replace(R.id.fragmentTwo, baseFragment);
ft.commit();
}
rotateFromSingleToDual = false;
return;
}
rotateFromSingleToDual = false;
ft.replace(R.id.fragmentOne, menuFragment);
ft.replace(R.id.fragmentTwo, baseFragment);
}
ft.commit();
}
This works, at least from time to time. However, many times I get java.lang.IllegalStateException: Fragment already added: MenuFragment (....)
Can anyone please give me some pointers as to how to better implement this? My current code is not pretty at all, and I'm sure many developers out there want to achieve exactly this.
Thanks in advance!
A common way to implement this scenario is to only use the fragment stack when in a mode that shows multiple fragments. When you're in the single fragment mode, you start a new activity that's sole job is to display the single fragment and take advantage of the activity back stack.
In your case you'll just need to remember the currently selected spot on rotate to set it as an argument when starting the new activity.
It's explained much better here:-
http://developer.android.com/guide/components/fragments.html
Hope that helps.
Since Tab Activity is deprecated I'm trying to implement tabs with fragments. As you can see in the lots of StackOverFlow questions back stack is an issue when you work with fragments and which has its own back stack.
So the thing I'm trying to do is, there is a fragment in each tab and this fragment can call another fragment within the same tab and its also the same for the other tabs.
Since there is only one activity then there is only one back stack for whole application. So I need to create my custom back stack which is separated for each tab. It's also the same common idea in the other questions. I need to find a way to create custom back stack but I couldnt find any example to take a look.
Is there any tutorial or any example piece of code doing something similar ? Thanks in advance.
There is a backstack for the whole application, but there is also a backstack for fragments.
Perhaps have a read of this:
http://developer.android.com/guide/components/fragments.html#Transactions
When you perform a fragment transaction (add, replace or remove), you can add that transaction to the backstack.
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragmentContainer, fragment);
ft.addToBackStack(null);
ft.commit();
And now when you press back, the latest fragment will be 'popped' from the fragment backstack.
You could also override the onBackPressed(), and manage your fragment backstack there. (I'm currently having trouble trying to work out how to do this efficiently).
Anyway, there are several calls available from the FragmentManager to do with the backstack. The most useful to me being:
FragmentManager.getBackStackEntryCount()
and
FragmentManager.PopBackStack()
Sorry for late answer, but this might help somebody. I have added functionality like this in my project. I used fragment tabhost to add backstacks within it. Main logic will remain same in others.
Basically I took,
Stack<String> fragmentStack = new Stack<>();
boolean bBackPress = false;
String strPrevTab;
FragmentTabHost tabHost;
strPrevTab = "tag"; // Add tag for tab which is selected by default
tabHost.setOnTabChangedListener( new TabHost.OnTabChangeListener() {
#Override
public void onTabChanged( String tabId ) {
if ( !bBackPress ) {
if ( fragmentStack.contains( tabId ) ) {
fragmentStack.remove( tabId );
}
fragmentStack.push( strPrevTab );
strPrevTab = tabId;
}
}
} );
#Override
public void onBackPressed() {
if ( fragmentStack.size() == 0 ) {
finish();
} else {
strPrevTab = fragmentStack.pop();
bBackPress = true;
tabHost.setCurrentTabByTag( strPrevTab );
bBackPress = false;
}
}
Hope this helps!