Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fragments being inflated with old data, when going back to an activity that was stopped

Activity A has fragments. When it starts an intent to activity B, then when B.finish(), A executes onCreate() again.

But this time, even though A.onCreate() has a new PacksPagerAdapter and a new ViewPager, the fragments are shown with old data.

I can see that that onCreateView() is executed for each fragment, but it still has the old arguments since the static newInstance() wasn't called. Arguments are created when FragmentPagerAdapter's getItem(position) is called.

Here's how it's implemented -

public class PackActivity extends Activity {
    ...
    PacksPagerAdapter mPacksPagerAdapter;
    ViewPager mViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mPacksPagerAdapter = new MyPagerAdapter(getFragmentManager());

        // Set up the ViewPager with the sections adapter.
        mViewPager = (ViewPager) findViewById(R.id.pager);
        mViewPager.setAdapter(mPacksPagerAdapter);
        mViewPager.setOffscreenPageLimit(PACK_PAGES - 1);

        mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
                    @Override
                    public void onPageSelected(int position) {
                        actionBar.setTitle(...);
                    }
                });

    }
    ...
}

public class MyPagerAdapter extends FragmentPagerAdapter {
...
    @Override
    public Fragment getItem(int position) {
        // getItem is called to instantiate the fragment for the given page.
        // Return a PlaceholderFragment (defined as a static inner class
        // below).
        return PackFragment.newInstance(myData);
    }
...
}

public class PackFragment extends Fragment {
...
    public static PackFragment newInstance(PackInfo pack) {
        PackFragment fragment = new PackFragment();

        Bundle bdl = new Bundle(2);
        bdl.putSerializable(EXTRA_PACK, pack);
        bdl.putInt(EXTRA_PACK_POSITION, pack.packNumber);
        fragment.setArguments(bdl);

        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View packView = inflater.inflate(R.layout.pack_fragment, container, false);

        // this has the old argument, since newInstance(...) wasn't called      
        PackInfo pack = (PackInfo) getArguments().getSerializable(EXTRA_PACK);
        ...

        return packView;
    }

...
}

Any idea why new fragments aren't being instantiated on the 2nd creation of activity A?

Update - Activity-Fragments lifecycle

The answer is, as Tal said, is that activity is being restored, so old fragments are being reattached instead of creating new ones.

After spending a lot of time studying this, I've found that there are typically 3 scenarios for activity-fragments lifecycle. Here are the scenarios, the lifecycle and how to reload fragment's old data:

New activity

Lifecycle: onCreate() ==> onResume() ==> Fragments are created and attached

No need to reload.

Restored activity

Lifecycle: onCreate() ==> Fragments inflated with old data and reattached ==> onResume()

In onResume(), remove all fragments and create new ones, either manually or automatically using an adapter -

        // Remove all fragments
        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        for (int i = BuildConfig.PACK_PAGES - 1; i >= 0; i--) {
            Fragment f = findFragmentByPosition(i);
            if (f != null)
                fragmentTransaction.remove(f);
        }
        fragmentTransaction.commit();

        // This will force recreation of all fragments
        viewPager.setAdapter(packsPagerAdapter);

Resumed activity

Lifecycle: onResume()

Same as activity restore, remove old fragments and recreate.

Note: Some OS versions always restores activity, even when opening a SettingsActivity for a few seconds, and other (older) versions will always resume.

like image 625
Kof Avatar asked Oct 20 '22 02:10

Kof


2 Answers

my answer will be better if you'll post the fragment transaction you commit in activity A.

not sure that you know it or not - if Activity A is re-created when it pop back from back stack - it means that it restored from previous instance state.

in that case, you should not perform the transaction again, because - it already happens automatically via the super.onCreate() Activity method. in fact, if you'll perform the fragment trasaction in that case - you'll cause the same fragment to be added twice (2 instances)

you can know if currently onCreate() been called from restoring instance state by checking if savedInstanceState parameter is null or not.

my assumption is that you are not checking savedInstanceState, and performing the transaction anyway.

if I'm right, then the following change should fix the fragment duplication problem:

instead of:

public static class ActivityA extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        performFragmentTransaction();

}

write:

public static class ActivityA extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (savedInstanceState == null) {
           performFragmentTransaction();
        }
}

more info at - http://developer.android.com/guide/components/fragments.html

like image 188
Tal Kanel Avatar answered Oct 23 '22 11:10

Tal Kanel


When the user moves away from activity A to B, the FragmentPagerAdapter detaches the fragments in A from user interface and does not destroy the instances so that they can be reused when user returns back to A latter.

I'm not sure why onCreate of A is called after B finishes. If you don't want the old fragments to be attached to the user interface, instead you want them to be recreated, then you need to explicitly destroy them.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mPacksPagerAdapter = new MyPagerAdapter(fragmentManager);

    // Set up the ViewPager with the sections adapter.
    mViewPager = (ViewPager) findViewById(R.id.pager);

    // Remove the fragments if they already exist.
    FragmentManager fragmentManager = getFragmentManager();
    FragmentTransaction currentTransaction = fragmentManager.beginTransaction();

    int count = mPacksPagerAdapter.getCount();
    for(int position = 0; position < count; position++) {
        long itemId = mPacksPagerAdapter.getItemId(position);
        // fragment name format maintained by adpater: "android:switcher:" + viewId + ":" + id
        String name = "android:switcher:" + mViewPager.getId() + ":" + itemId;
        Fragment fragment = fragmentManager.findFragmentByTag(name);
        if(fragment != null) {
            // if fragment instance exists, destroy it.
            currentTransaction.remove(fragment);
        }
    }
    // commit all the operations with state loss.
    currentTransaction.commitAllowingStateLoss();
    // immediately execute the operations
    fragmentManager.executePendingTransactions();

    // Rest of your code follows here.
    ....
}
like image 24
Manish Mulimani Avatar answered Oct 23 '22 11:10

Manish Mulimani