I am working on single activity based principle. But I am facing a problem when the same fragment is open again because its again adds in fragment backstack entry. So backstack contains multiple backstack entries for same fragment. This creates problem on back navigation.
Example :- A|B|C|D|A|C|A
So when I press back key same fragment is displaying multiple times. Is there any way to reuse the existing fragment from backstack entry.
I am managing my backstack like this :-
fragmentManager.beginTransaction().setCustomAnimations(R.anim.fragment_enter,
R.anim.fragment_exit, R.anim.pop_enter, R.anim.pop_exit).
add(R.id.frameLayout, fragment).addToBackStack(backStateName).commit();
Any kind of help will be appreciated.
private void createOrResumeFragment(String fragmentTag){
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
boolean fragmentPopped = manager.popBackStackImmediate (fragmentTag, 0);
Fragment fragment = manager.findFragmentByTag(fragmentTag);
if(!fragmentPopped && fragment == null){
//Create an new instance if it is null and add it to stack
fragment = new MyFragment();
ft.addToBackStack(fragmentTag);
}
ft.replace(R.id.framelayout, fragment);
ft.commit();
}
Trying this using the fragment list
See the Answer Here
Initialize the fragments list
static List<String> fragments = new ArrayList<String>();
on Start of first fragment on Activity add this
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().replace(R.id.frame, new AFragment()).commit();
fragments.add("package.fragment.AFragment");
}
Code to fragment change and take in back stack
String backStateName = fragment.getClass().getName();
FragmentManager manager = getSupportFragmentManager();
//fragment not in back stack, create it.
FragmentTransaction ft = manager.beginTransaction();
if(!fragments.contains(backStateName)) {
// ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
// ft.setCustomAnimations(R.anim.fade_in, R.anim.fade_out);
ft.replace(R.id.frame, fragment);
ft.addToBackStack(backStateName);
ft.commit();
fragments.add(backStateName);
System.out.println("backStateName" + fragments);
}
else
{
ft.replace(R.id.frame, fragment);
ft.commit();
}
onBackpressed
#Override
public void onBackPressed() {
if(fragments.size()>0)
{
fragments.remove(fragments.size()-1);
}
super.onBackPressed();
}
for back remove stack
final android.app.FragmentManager fm = getFragmentManager();
fm.addOnBackStackChangedListener(new android.app.FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if (getSupportFragmentManager().getBackStackEntryCount() ==0) {
// dLayout.closeDrawers();
finish();
}
else
{
// dLayout.closeDrawers();
}
}
});
Before adding or replacing the fragment on backstack, check that if the fragment already in backstack or not.
boolean fragmentPopped = fragmentManager.popBackStackImmediate(backStateName, 0);
if (fragmentPopped) {
// fragment is popped from backStack
} else {
// add or replace your fragment here
}
public void changeFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment currentVisibleFragment = fragmentManager.findFragmentById(R.id.container);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.container, fragment, fragment.getClass().getSimpleName());
if (!currentVisibleFragment.getClass().getSimpleName().trim().equalsIgnoreCase(fragment.getClass().getSimpleName().trim())) {
for (int i = fragmentManager.getBackStackEntryCount() - 1; i > 0; i--) {
if (fragmentManager.getBackStackEntryAt(i).getName().equalsIgnoreCase(fragment.getClass().getSimpleName())) {
fragmentManager.popBackStack(fragmentManager.getBackStackEntryAt(i).getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
fragmentTransaction.addToBackStack(fragment.getClass().getSimpleName());
} else {
fragmentManager.popBackStack();
fragmentTransaction.addToBackStack(fragment.getClass().getSimpleName());
}
fragmentTransaction.commit();
}
boolean doubleBackToExitPressedOnce = false;
#Override
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
super.onBackPressed();
} else {
if (doubleBackToExitPressedOnce) {
super.onBackPressed();
finish();
return;
}
this.doubleBackToExitPressedOnce = true;
Toast.makeText(this, "Are you sure you want to exit?", Toast.LENGTH_SHORT).show();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
doubleBackToExitPressedOnce = false;
}
}, 2000);
}
}
Call the method to replace fragment with single entry in backstack
changeFragment(new YourFragmentClassName());
Related
When i change tab of Bottom Bar for example when the home tab is selected and when i change the tab and select category tab in first time load data and it is not problem but when select again home tab again reload fragment and load data again.
how to resolve this problem and save state fragment.
My code:
bottomBar.setOnTabSelectListener(new OnTabSelectListener() {
#Override
public void onTabSelected(int tabId) {
displayFragment(tabId);
}
});
private void displayFragment(int id) {
Fragment selectedFragment = null;
switch (id) {
case R.id.tab_home:
selectedFragment = HomeFragment.getInstance();
break;
case R.id.tab_category:
selectedFragment = CategoryFragment.getInstace();
break;
}
if (selectedFragment != null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.contentContainer, selectedFragment);
transaction.commit();
}
}
There are multiple ways you can handle preserving and updating your fragments data throughout its life cycle. I would look into the lifecycle of activity fragments to better grasp what applies to your context. From 30k feet, after you call .replace() the fragment's override onCreateView() should fire, here you can handle your update logic.
As I mentioned, context is big here. If you end up changing your transaction logic, it could impact the life cycle of your fragments. For example, if you were hiding/showing your fragments, using the override onHidden() or onResume() could be a better solution.
In addition, you also should consider how your navigation logic impacts and interacts with your activity's/fragment's backstack. In your current logic, you're creating a new instance of the fragment each time a tab is selected.
This is answer my question :
public class MainActivity extends AppCompatActivity {
FeedFragment feedFragment;
PublishFragment publishFragment;
ServicesFragment servicesFragment;
SearchFragment searchFragment;
ProfileFragment profileFragment;
FragmentTransaction ft;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomBar bottomBar = (BottomBar) findViewById(R.id.bottomBar);
feedFragment = new FeedFragment();
publishFragment = new PublishFragment();
servicesFragment = new ServicesFragment();
searchFragment = new SearchFragment();
profileFragment = new ProfileFragment();
bottomBar.setOnTabSelectListener(new OnTabSelectListener() {
#Override
public void onTabSelected(#IdRes int tabId) {
if (tabId == R.id.feed_icon) {
GeneralUtils.logPrint("feed");
ft = getSupportFragmentManager().beginTransaction();
if (feedFragment.isAdded()) {
ft.show(feedFragment);
} else {
ft.add(R.id.fragment_container, feedFragment);
}
hideFragment(ft,publishFragment);
hideFragment(ft, servicesFragment);
hideFragment(ft,searchFragment);
hideFragment(ft,profileFragment);
ft.commit();
}
if (tabId == R.id.publish_icon) {
GeneralUtils.logPrint("publish");
ft = getSupportFragmentManager().beginTransaction();
if (publishFragment.isAdded()) {
ft.show(publishFragment);
} else {
ft.add(R.id.fragment_container, publishFragment);
}
hideFragment(ft,feedFragment);
hideFragment(ft, servicesFragment);
hideFragment(ft,searchFragment);
hideFragment(ft,profileFragment);
ft.commit();
}
if (tabId == R.id.services_icon) {
GeneralUtils.logPrint("services");
ft = getSupportFragmentManager().beginTransaction();
if (servicesFragment.isAdded()) {
ft.show(servicesFragment);
} else {
ft.add(R.id.fragment_container, servicesFragment);
}
hideFragment(ft,feedFragment);
hideFragment(ft, publishFragment);
hideFragment(ft,searchFragment);
hideFragment(ft,profileFragment);
ft.commit();
}
if (tabId == R.id.search_icon) {
GeneralUtils.logPrint("search");
ft = getSupportFragmentManager().beginTransaction();
if (searchFragment.isAdded()) {
ft.show(searchFragment);
} else {
ft.add(R.id.fragment_container, searchFragment);
}
hideFragment(ft,feedFragment);
hideFragment(ft,publishFragment);
hideFragment(ft,servicesFragment);
hideFragment(ft,profileFragment);
ft.commit();
}
if (tabId == R.id.profile_icon) {
GeneralUtils.logPrint("profile");
ft = getSupportFragmentManager().beginTransaction();
if (profileFragment.isAdded()) {
ft.show(profileFragment);
} else {
ft.add(R.id.fragment_container, profileFragment);
}
hideFragment(ft,feedFragment);
hideFragment(ft, publishFragment);
hideFragment(ft,servicesFragment);
hideFragment(ft,searchFragment);
ft.commit();
}
}
});
}
private void hideFragment(FragmentTransaction ft, Fragment f) {
if (f.isAdded()) {
ft.hide(f);
}
}
}
From my main activity, pressing Settings button opens an AppCompatActivity with FrameLayout as the container for the fragments. In OnCreate method, I'm adding the fragment which is a PreferenceScreen container.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings);
Toolbar toolbar = (Toolbar) findViewById(R.id.custom_toolbar);
setSupportActionBar(toolbar);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
ab.setTitle("Settings");
}
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = null;
if (savedInstanceState == null) {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragment = SettingsFragment.newInstance(null);
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}
}
Pressing again another button called Options, opens replaces fragment with Options fragment adding the current class to backstack.
#Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref) {
FragmentManager manager = getSupportFragmentManager();
SettingsSubscreenFragment fragment = null;
Bundle args = new Bundle();
switch (pref.getKey()) {
case "pref_key_rejection_options":
getSupportActionBar().setTitle("Rejection Options");
fragment = SettingsSubscreenFragment.newInstance("Options");
break;
}
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, pref.getKey());
if (fragment != null) {
fragment.setArguments(args);
}
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.fragment_container, fragment, pref.getKey());
ft.addToBackStack(null);
ft.commit();
return true;
}
When I press the Up Button, I'm just replacing the Options fragment to the first fragment, Settings, which does not pop the back stack.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
getSupportActionBar().setTitle("Settings");
FragmentManager fragmentManager;
FragmentTransaction ft;
Fragment fragment = null;
if (getSupportFragmentManager().findFragmentByTag("pref_key_options") != null) {
fragmentManager = getSupportFragmentManager();
ft = fragmentManager.beginTransaction();
fragment = SettingsFragment.newInstance(null);
ft.replace(R.id.fragment_container, fragment);
ft.commit();
fragmentManager.getFragments().clear();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
return super.onOptionsItemSelected(item);
}
So, opening the Options fragment again will add another to back stack and onBackPress, it only pops the topmost and leaves its shadow overlapping the other one registered in back stack.
#Override
public void onBackPressed() {
int backStackCount = getSupportFragmentManager().getBackStackEntryCount();
if (backStackCount >= 1) {
getSupportActionBar().setTitle("Settings");
getSupportFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
The almost the same situation is like from here, third picture of the question, but I can't make it work with their suggestions.
What am I doing wrong and what I can do to make this work? Thanks a lot.
Check FragmentTransaction , SettingsSubscreenFragment and SettingsFragmentare extending android.support.v4.app.Fragment . If not , try
class YourFragments extends android.support.v4.app.Fragment
in your fragment classes.
I have some fragments which will be replaced by following method. I think there's something wrong with my code because I want to prevent from adding multiple times a fragment into the back stack. If I click on fragment B twice, all instances will be added to the back stack and pressing back button will be passed through the two created instances.
public void replaceFragment(Fragment fragment, boolean addToBackStack, boolean customAnimation) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
String tag = fragment.getClass().getSimpleName();
if (customAnimation) {
transaction.setCustomAnimations(R.anim.slide_in_bottom, R.anim.slide_out_bottom, R.anim.slide_in_bottom, R.anim.slide_out_bottom);
}
transaction.replace(R.id.fragment_container, fragment, tag);
// remove from back stack if exists
// always return false!
boolean f = manager.popBackStackImmediate(tag, 0);
if (addToBackStack) {
transaction.addToBackStack(tag);
}
transaction.commit();
}
Keep it simple and just add to the back stack if needed.
If the Fragment being added is the same class as the current Fragment, don't add to the back stack:
public void replaceFragment(Fragment frag) {
FragmentManager manager = getSupportFragmentManager();
if (manager != null){
FragmentTransaction t = manager.beginTransaction();
Fragment currentFrag = manager.findFragmentById(R.id.content_frame);
//Check if the new Fragment is the same
//If it is, don't add to the back stack
if (currentFrag != null && currentFrag.getClass().equals(frag.getClass())) {
t.replace(R.id.content_frame, frag).commit();
} else {
t.replace(R.id.content_frame, frag).addToBackStack(null).commit();
}
}
}
try this on your activity onBackPressed method :
#Override
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
if (fm.getBackStackEntryAt(fm.getBackStackEntryCount() - 1).getName().equals("your fragment tag")) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
I have an activity with two fragments. In this one fragment is replaced by the other. I have added both fragments to the backstack.
#Override
public void showGatherIntentions() {
gatherIntentionView = GatherIntentionsFragment.newInstance(GatherIntentionContract.INTENTION_OPTION_NONE);
FragmentManager manager = getSupportFragmentManager();
boolean fragmentPopped = manager.popBackStackImmediate (GATHER_INTENTIONS, 0);
if (!fragmentPopped) {
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(0, 0, R.anim.slide_in_right, R.anim.slide_out_right)
.replace(R.id.contentFrame, (Fragment) gatherIntentionView)
.addToBackStack(GATHER_INTENTIONS)
.commit();
currentPosition++;
}
}
code for replacing first fragment
#Override
public void showCollectPreferences() {
collectPreferencesView = CollectPreferencesFragment.newInstance(CollectPreferencesContract.DEFAULT_OPTION);
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_left,
R.anim.slide_in_right, R.anim.slide_out_right)
.replace(R.id.contentFrame, (Fragment) collectPreferencesView)
.addToBackStack(COLLECT_PREFERENCES)
.commit();
currentPosition++;
}
code for replacing the second one
#Override
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() == 1) {
finish();
} else {
super.onBackPressed();
currentPosition--;
}
}
State is saved for second fragment but not for first fragment i.e whenever i am on second fragment and press back button its onCreateView method is called .
Any help would be appreciated. Thanks in advance!
I am using a navigation drawer.So when my application starts i am calling the homeFragment and keeping it in the backstack. Now if the user selects any options from the navigation drawer i am opening the respective fragment but without adding them to backstack. So what i want is that even when user has open 10 fragment, on pressing back they should be taken back to homeFragment only. But with my code the app exits on pressing back even homeFragment is in the backstack.
Code to openFragments
public static void replaceFragment(FragmentActivity activity, Fragment fragment, boolean addToBackStack) {
try {
String backStateName = fragment.getClass().getName();
String fragmentTag = backStateName;
FragmentManager manager = activity.getSupportFragmentManager();
boolean fragmentPopped = manager.popBackStackImmediate(fragmentTag, 0);
if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null) {
FragmentTransaction ft = manager.beginTransaction();
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right);
ft.replace(R.id.frag_container, fragment, fragmentTag);
if (addToBackStack) {
ft.addToBackStack(backStateName);
}
ft.commit();
}
} catch (Exception e) {
e.printStackTrace();
}
}
From MainActivity
private void init() {
setUpToolBar();
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
drawerFrag = (NavigationDrawerFrag) getSupportFragmentManager().findFragmentById(R.id.frag_nav_drawer);
drawerFrag.setUp(drawerLayout, toolbar, R.id.frag_nav_drawer);
CommonFunctions.replaceFragment(this, new HomeFrag(), true);
}
On NavigationDrawer Item click
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (position == 1) {
CommonFunctions.replaceFragment(getActivity(), new ProfileFrag(), false);
} else if (position == 2) {
CommonFunctions.replaceFragment(getActivity(), new CelebrityFrag(), false);
} else if (position == 4) {
CommonFunctions.replaceFragment(getActivity(), new AboutUsFrag(), false);
} else if (position == 5) {
CommonFunctions.replaceFragment(getActivity(), new TermsAndConditions(), false);
}
lvNavItems.setItemChecked(position, true);
//lvNavItems.setSelection(position);
lvNavItems.setSelected(true);
mDrawerLayout.closeDrawer(fragContainer);
}
Add onBackPressed() method in activity to check backstack and if entry is more then zero so call popbackstack method.
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() != 0) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
//Put this in your onNavigationItemSelected of MainActivity
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
//FrameLayout where you load your home fragment tools:layout="#layout/frag_home"
ft.replace(R.id.content_frame,nav_selected);
if(getSupportFragmentManager().getBackStackEntryCount() != 0){
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
getSupportFragmentManager().popBackStack();
}
//Don't add Home screen
if(id != R.id.nav_home){
ft.addToBackStack(null);
}
ft.commit();