I started a new project using tabbed activity of Android Studio. It created an activity with ViewPager and 3 fragments = fragment1, fragment2 and fragment3.
I changed fragment1 to have a listView and a TextView, that I initialize when the fragment is created. The data in the listView is read from an SQL database.
When the application starts, fragment1 is displayed and the listView shows the data read from SQL.
When I swipe from fragment1 to fragment2 and back, fragment1 shows the listview properly. However, when I swipe to fragment3 and back to fragment1 the listview data is lost and I get a blank screen, although the textview shows properly.
When I change the application to have only 2 fragments it does not happen and I can swipe from fragment1 to fragment2 and back many times without any loss of data. The moment I add the 3rd fragment, the data is lost on first swipe to fragment3, but is not lost if I swipe between fragment 1 and fragment2 only.
Any idea why is it happening?
This is happening because by default ViewPager has a function named setOffscreenPageLimit(int i). This tells ViewPager how many of the Fragments need to store in memory. By default it is '2'. So when you swipe to Fragment 2 from Fragment 1 it will still keep Fragment 1 in memory and won't destroy it from memory. But when you swipe to Fragment 3 it will have Fragment 2 and Fragment 3 in memory so it will remove Fragment 1.
That's why when you come back to your first Fragment it have lost its views or data.
Try to use
mViewPager.setOffscreenPageLimit(your total fragment count +1);
Or much better solution
Do not replace your fragment when you swipe in ViewPager. Push it to the backstack and when you comeback to it, Check for the backstack and if fragment is there then just Pop it and display it.
Here is the code:
public void pushFragments(String tag, Fragment fragment) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
if (manager.findFragmentByTag(tag) == null) {
ft.add(R.id.frame_container, fragment, tag);
}
String tagOne="com........Frag_one"; //your fragment class name
String tagTwo="com........Frag_two";
String tagThree="com........Frag_three";
String tagFour="com........Frag_profile";
Fragment fragmentOne = manager.findFragmentByTag(tagOne);
Fragment fragmentTwo = manager.findFragmentByTag(tagTwo);
Fragment fragmentThree = manager.findFragmentByTag(tagThree);
Fragment fragmentFour = manager.findFragmentByTag(tagFour);
manager.executePendingTransactions();
// Hide all Fragment
if (fragmentOne != null) {
ft.hide(fragmentOne);
}
if (fragmentTwo != null) {
ft.hide(fragmentTwo);
}
if (fragmentThree != null) {
ft.hide(fragmentThree);
}
if (fragmentFour != null) {
ft.hide(fragmentFour);
}
// Show current Fragment
if (tag.equals(tagOne)) {
if (fragmentOne != null) {
ft.show(fragmentOne);
}
}
if (tag.equals(tagTwo)) {
if (fragmentTwo != null) {
ft.show(fragmentTwo);
}
}
if (tag.equals(tagThree)) {
if (fragmentThree != null) {
ft.show(fragmentThree);
}
}
if (tag.equals(tagFour)) {
if (fragmentFour != null) {
ft.show(fragmentFour);
}
}
ft.commit();
}
And Here is how you call it:
Fragment fragment = new Frag_one();
pushFragments(fragment.getClass().getName(),fragment);
Issue solved. The problem is due to the adapter keeping 3 fragments in memory, as pointed by #Daniel Nugent. The data in the pages is loaded from DB so that when the view is recreated, when fragments are destroyed and recreated as the user swipes, the listview adapter has lost the data, and one has to repopulate it. The problem does not appear on simple examples because usually the data presented is static and is loaded in the onCreateView method.
Related
In my main activity, i have a framelayout which is used to display fragments
now imagine, i have 3 fragment objects :
Fragment1 f1 = new Fragment1();
Fragment2 f2 = new Fragment2();
Fragment3 f3 = new Fragment3();
and i set this fragment using :
fragTrans.replace(android.R.id.content, f1);
fragTrans.addToBackStack(null);
fragTrans.commit();
now at different point of times, the app might have one of the three fragments in the framelayout (number of fragments might vary).
So, at some point if i want to identify which of the fragments if being currently displayed in the framelayout, how can i do that ?
My purpose :
the fragment2 has say 2 states, where boolean state can be either true or false, if the state is true, and the back button is pressed i want to do something and set state to false and the if the back is pressed i'll call super.onBackPressed() but if it is set to true and if currently fragment 3 is visible, first i want to go to fragment 2, then change the state to false and then super.onBackPressed()
so that will make it piece of cake if i can identify which fragment is currently visible
Try this
Fragment fragment = (Fragment)supportFragmentManager.findFragmentById(R.id.fragment_container)
Your Solution is here
1. First of you replace the fragment replace line with this line.
fragTrans.replace(android.R.id.content, f1,f1.getClass.getName());
Here f1.getClass,getName() is key of current Fragment. it Gives you name of fragment which is replace or current replace fragment.
FragmentManager manager = getSupportFragmentManager();
Fragment1 fragment1 = (Fragment1) manager.findFragmentByTag(Fragment1.class.getName());
Fragment2 fragment2 = (Fragment2) manager.findFragmentByTag(Fragment2.class.getName());
Fragment3 fragment3 = (Fragment3) manager.findFragmentByTag(Fragment3.class.getName());
if (fragment1 != null) {
Log.e(TAG, "Current fragment is Fragment1");
}else if(fragment2 != null) {
Log.e(TAG, "Current fragment is Fragment2");
}else if(fragment3 != null) {
Log.e(TAG, "Current fragment is Fragment3");
} else {
Log.e(TAG, "Fragment 1-2-3 is null");
}
If you do something on first time onBackPress Follow the code below,
#Override
public void onBackPressed() {
if (isBackPress) { //Default is false
super.onBackPressed();
} else {
// On First time click do something
isBackPress=true;
}
}
I am using NavigationDrawer in my application and each menu item in drawer is a fragment.Whenever user chooses a menu item I replace the current fragment in the main container with the requested one but it recreates the fragment every-time, so i updated my code to reuse the existing fragments instead of creating them again and again as content of fragments remain same. My updated code to show fragment is :
public void showTabFragment() {
TabFragment Tf = (TabFragment) mFragmentManager.findFragmentByTag(Constants.TAB_FRAGMENT);
mFragmentTransaction = mFragmentManager.beginTransaction();
if (Tf != null) {
mFragmentTransaction.replace(R.id.containerView, Tf, Constants.TAB_FRAGMENT);
} else {
mFragmentTransaction.replace(R.id.containerView, new TabFragment(), Constants.TAB_FRAGMENT);
}
mFragmentTransaction.commit();
}
In above code I am trying to get fragments by Tag but it always returns null and executes the else case(new fragment).Could someone please guide me what am I doing wrong in my code?
I guess the code you've shown is for one of your menu fragment? If that's the case, what is probably happening is every time you open a menu item, the container is replaced with the new fragment(say, Fragment B) with its new tag(say, TAG 'B'). So, when you try to open the previous fragment(say, Fragment A) using it's tag(TAG 'A'), it won't be there, because that's what you replaced.
One possible solution is to hold references to the fragment as they are created, in, say a hashmap, and reuse them instead.
private HashMap<String, Fragment> menuFragments = new HashMap<>();
public void showMenu(String fragmentID)
{
MenuFragment fragment = menuFragments.get(fragmentID);
if(fragment == null)
{
fragment = new MenuFragment(); //Create the respective menu fragment based on the ID.
menuFragments.put(fragmentID, fragment);
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.containerView, fragment, fragmentID);
transaction.commit();
}
Say I've pushed three fragments onto the stack: Fragment A, B, and C.
Goal: When I press back on Fragment C I want to have some hook into determining if Fragment B is showing and call frag.onShow().
Thought process: I added an onBackStackChangedListener in order to determine when fragments were added or removed from the stack. Here I check whether the current fragment is equal to the new one (pushed) or different (popped).
Problem: Both frag and getCurrentFragment() are returning the current fragment AFTER Fragment C has been dismissed. So here the new fragment is Fragment B but getCurrentFragment() is also returning Fragment B so it doesn't think it's a pop.
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
FragmentManager fm = getSupportFragmentManager();
int count = fm.getBackStackEntryCount();
if (count > 0) {
String name = fm.getBackStackEntryAt(count - 1).getName();
MyFragment frag = (MyFragment) fm.findFragmentByTag(name);
MyFragment currFrag = getCurrentFragment();
if (frag != currFrag) {
if (frag != null) {
frag.onShow();
}
}
}
}
});
public MyFragment getCurrentFragment() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (fragment instanceof MyFragment) {
return (MyFragment) fragment;
}
return null;
}
Question: Is there a way to get the last popped fragment? Or is there a better way to do what I'm trying to do? I can always keep some variable around to determine if I'm popping or pushing but I was hoping for a less hacky feeling way.
Thanks.
Thank you #DeeV for helping me understand fragment visibility.
If you're just stacking Fragments on top of each other, then they are technically all still "visible" and active as far as the FragmentManager is concerned. Assuming that your fragments are only visible at one time, you can call FragmentTransaction#hide(fragment) to hide a Fragment. Fragment#onHiddenChanged() will be called both when you hide it and when you unhide it.
I'll be transferring my code to what they suggested. I also realized that if you do need to do it manually like I was doing earlier, a variable of the last fragment count will help determine whether it was a push or a pop.
In my android application, a fragment will be added to the activity by a certain action (for example, the action bar menu).
This is the code I add the fragment:
case R.id.action_add_box:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.place, BoxEditFragment.newInstance(null, null));
ft.addToBackStack(null);
ft.commit();
break;
Now once the user hit the action menu with id action_add_box two times, then he have to hit the back two times to close the fragment which is not expected.
Is it possible to avoid this?
For example, once user hit the action menu, do nothing if the fragment have been already visible to the user?
And one more question, there are some EditTexts in the fragment, once user complete, I will submit the data and close the fragment, however user may need to open the fragment again, and I want to keep the value of the EditText as last entered by user. Now I save the values when the fragment are detached and reset the value when created using the savedInstanceState.
Also create a new instance of the fragment for each action command is a waste of memory, I wonder if I can use only one fragment instance, then I may not need to save/reset the values manually?
you can use singleton parttern to keep one instance of fragment for eg:
public class MyFragment extends Fragment{
public static MyFragment oneInstance = null ;
private MyFragment(){
super();
}
public static MyFragment getInstance(){
if (oneInstance == null ){
synchronized (MyFragment.class){
if ( oneInstance == null ){
oneInstance = new MyFragment();
}
}
return oneInstance ;
}
}
the above code is also thread safe
MyFragment frag= (MyFragment )getFragmentManager().findFragmentById(R.id.your_fragment_layout);
if(frag == null){
// fragment is not visible
}else{
// fragment is visible
}
I've five fragments in my activity. On the click of fragment drawer list, I'm calling setFrament() method.
It removes the previous fragments from the activity, and adds the new one as per the requirement.
Here's my code for setFragment method.
protected void setFragment(final int position) {
// Remove currently active fragment
if (mActiveFragment != null) {
Fragment previous;
while ((previous = mFragmentManager
.findFragmentByTag(mActiveFragment.toString())) != null) {
mFragmentManager.beginTransaction().remove(previous).commit();
Log.e("in loop", "stuck");
}
}
// It's enum, generated according to position
FragmentType type = getFragmentType(position);
Fragment fragment = mFragmentManager.findFragmentByTag(type.toString());
if (fragment == null) {
fragment = createFragment(type);
}
mFragmentManager
.beginTransaction()
.replace(R.id.containerFrameLayout, fragment,
type.toString()).commit();
// Sets the current selected fragment checked in
// Drawer listview
mFragmentDrawerList.setItemChecked(position, true);
// set Actionbar title
getActionBar().setTitle(type + "");
mActiveFragment = type;
}
Here, while changing the fragment, it keeps prining "stuck" in while loop forever,
my question is,
why remove(Fragment) method doesn't remove the previous fragment from the activity ?
commit() is an asynchronous call that is only executed when the Android system regains control. The fragments won't be removed until some time after you leave the method.
If you want to remove the previous fragment (assuming there's only one), then you can add them to the backstack. Then you can call popBackStack(null, 0) which will pop everything on the back stack. The side catch though is pressing the "back" button will also pop the backstack if the user were to do that. You'll have to override the onBackPressed() and handle it yourself if you don't want that to happen.
EDIT:
One method would be to keep track of all IDs or TAGs and call remove on them individually.
LinkedList<Integer> fragmentIds = new LinkedList<Integer>();
/*** Add fragment to FragmentManager ***/
fragmentIds.add(/** ID of fragment */);
/** Removing all fragments from FragmentManager **/
FragmentManager fm = getFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
Fragment fragToRemove;
for(Integer id : fragmentIds)
{
fragToRemove = fm.findFragmentById(id);
transaction.remove(fragToRemove);
}
transaction.commit()
fragmentIds.clear();
However, you don't have to call remove on any of them so long as you use replace() method on the same container. replace() will pop the previous fragment and add in the new one. So long as the transaction isn't pushed to the backstack, the previous fragment is detached from the Activity and discarded.