Save/Restore fragments state android
Asked Answered
E

6

14

I am trying to create an app that uses Jack Wharton's ViewPager library (here) using only images for every page (something like Prixing (here) main screen). Everything works ok, except for the saveInstance in the Fragment.

In Jack Wharton's example, he stores the text in a String variable, named mContent, and restores it in onCreate, but in my case, what should I do? Save/Restore a bitmap?! Any objective answer would be much appreciated!

I'm quite new to this Fragment using applications, cause every example I look at, presents only the basics, and it's getting difficult upon more complex ones.

PS. If is it usefull to know, I'm using CirclePageIndicator.

Current Fragment Code here:

    public final class SpecialOfferFragment extends Fragment {

    private int imageResourceId;

    public static SpecialOfferFragment newInstance(int i) {

        //probably I'll use a bitmap(downloaded) as parameter instead of using static images
        SpecialOfferFragment fragment = new SpecialOfferFragment();

        fragment.imageResourceId = i;

        return fragment;
    }


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // if ((savedInstanceState != null) { }

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        ImageView image = new ImageView(getActivity());
        image.setImageResource(imageResourceId);
        image.setScaleType(ScaleType.FIT_XY);

        LinearLayout layout = new LinearLayout(getActivity());
        layout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
                LayoutParams.FILL_PARENT));

        layout.setGravity(Gravity.CENTER);
        layout.addView(image);

        return layout;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        //smth to save here..
    }
}

In this current state of the app, I get the following Exception:

04-12 07:28:17.760: E/AndroidRuntime(31903): FATAL EXCEPTION: main 
04-12 07:28:17.760: E/AndroidRuntime(31903): java.lang.NullPointerException
04-12 07:28:17.760: E/AndroidRuntime(31903): at android.support.v4.app.FragmentManagerImpl.saveFragmentBasicState(FragmentManager.java:1576)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.support.v4.app.FragmentManagerImpl.saveAllState(FragmentManager.java:1617)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.support.v4.app.FragmentActivity.onSaveInstanceState(FragmentActivity.java:481)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.Activity.performSaveInstanceState(Activity.java:1113)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.java:1188)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.ActivityThread.performStopActivityInner(ActivityThread.java:2804)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.ActivityThread.handleStopActivity(ActivityThread.java:2862)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.ActivityThread.access$900(ActivityThread.java:127)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1175)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.os.Handler.dispatchMessage(Handler.java:99)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.os.Looper.loop(Looper.java:137)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.ActivityThread.main(ActivityThread.java:4511)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at java.lang.reflect.Method.invokeNative(Native Method)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at java.lang.reflect.Method.invoke(Method.java:511)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:980)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:747)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at dalvik.system.NativeStart.main(Native Method)

This happens whenever I pause/stop the activity containing this fragment, like pushing the Home button.

**

EDIT with code:

**

 public class MainMenu extends FragmentActivity {

    //private List<CategoriesHolder> categoriesList = new ArrayList<CategoriesHolder>();
    //private CategoriesAdapter categoriesAdapter = null;
    //private GridView gv_mainmenu_categories;

    SpecialOfferFragmentAdapter mAdapter;
    ViewPager mPager;
    PageIndicator mIndicator;

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

        setContentView(R.layout.activity_mainmenu);

        linkUI();
        setAction();

        // just for testing
        String[] imagesUrls = null;

        mAdapter = new SpecialOfferFragmentAdapter(getSupportFragmentManager(),
                this, imagesUrls);

        //categoriesAdapter = new CategoriesAdapter(this, categoriesList);

        // used for Categories GridView
        //gv_mainmenu_categories.setAdapter(categoriesAdapter);

        // used for ViewPager
        mPager.setAdapter(mAdapter);
        mIndicator.setViewPager(mPager);

    }

    private void linkUI() {
        mPager = (ViewPager) findViewById(R.id.vp_mainmenu_special_offers);
        mIndicator = (CirclePageIndicator) findViewById(R.id.indicator);

        //gv_mainmenu_categories = (GridView) findViewById(R.id.gv_mainmenu_categories);

    }

    private void setAction() {

    }
} 

    public class SpecialOfferFragmentAdapter extends FragmentPagerAdapter implements
        IconPagerAdapter {

    private int[] mCarouselImages = new int[] { R.drawable.pic1,
            R.drawable.pic2, R.drawable.pic3, R.drawable.pic4, R.drawable.pic5

    };

    private String[] imagesUrls;
    private Context context;

    public static final int[] ICONS = new int[] {
            R.drawable.perm_group_calendar, R.drawable.perm_group_camera,
            R.drawable.perm_group_device_alarms, R.drawable.perm_group_location };

    private int mCount = mCarouselImages.length;

    public SpecialOfferFragmentAdapter(FragmentManager fm, Context context,
            String[] imagesUrls) {
        super(fm);
        this.context = context;
        this.imagesUrls = imagesUrls;
    }

    @Override
    public Fragment getItem(int position) {

        return SpecialOfferFragment.newInstance(mCarouselImages[position]);
    }

    @Override
    public int getCount() {
        return mCount;
    }

    @Override
    public int getIconResId(int index) {
        return ICONS[index % ICONS.length];
    }

    public void setCount(int count) {
        if (count > 0 && count <= 10) {
            mCount = count;
            notifyDataSetChanged();
        }
    }
}
Estradiol answered 12/4, 2013 at 7:18 Comment(6)
what you want to achieve with saving the instance?Pythoness
I editted my question so it has the Exception I get too. As I read on Android docs, I understood that I have to save the state of my fragment every time and restore it in OnCreate (maybe I missed smth, if so how can I solve that exception?).Estradiol
Please give info about where is that exception thrown in your code.Pythoness
I can't do that as Exception Log doesn't send me anywhere in app code as I double-click on logs (Eclipse). - maybe it's smth related to android.support.v4 jar, so it can't redirect me to the "problem code". I re-edit question with full code.Estradiol
So basically just to be clear, the thing which you want to achieve is to load images in ViewPager and if the user for example go to landscape/portrait (or goes to another activity / fragment) mode to stay at the current image, not to start from the beginning, is tht correct?Pythoness
An effective solution for save/restore fragments you can look [here][1] [1]: #22505827Uxoricide
P
11

To achieve the thing which you want, you should use integer value to store the current position in your ViewPager, and after that use this value to set the right position to your ViewPager.

For example do something like this in your FragmentActivity:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
  super.onSaveInstanceState(savedInstanceState);
  savedInstanceState.putInt("mMyCurrentPosition", mPager.getPosition());
}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
  super.onRestoreInstanceState(savedInstanceState);
  mMyCurrentPosition = savedInstanceState.getInt("mMyCurrentPosition");
  // where mMyCurrentPosition should be a public value in your activity.
}

which will saved your ViewPager's last position. And after that in your onResume() for example you can check

if(mMyCurrentPosition != 0){
    mPager.setCurrentItem(mMyCurrentPosition);
}

I think this should work , it's not a good practice to save images / bitmaps in bundle (and I don't think you are able to do it).

Pythoness answered 12/4, 2013 at 8:40 Comment(6)
ok that would probably solve the problem of getting last state of the PageViewer after it was paused/stopped, but I still get this anoying error (logged in my question) whenever I pause/stop my app.Estradiol
what you mean by pause/stop my app?Pythoness
I mean opening another activity / application, switching orientation, anything that could trigger the pause or stop action on this activity. It keeps triggering that Exception.Estradiol
try using FragmentStatePagerAdapter instead of FragmentPagerAdapter .Pythoness
oh, Gosh, at last it works. Thanks so much for your time spent with this question! One last request, it you could bear with me some more time xD. I tried your answer for saving last position, but it seems ViewPager doesn't have getPosition() method. Any hint on getting that position anyway?Estradiol
What If i wanted to save a textbox value and then on returning to the tab to enter that information back to the textbox?Streetman
P
2

Ok first id like to clear something up that might solve your confusion about savedinstancestate. Savedinstancestate can only handle primitives ( int, double, float, byte..) and the String object. This should be all you need but in some cases you want to save instances of other objects as well. For this you would need some other pattern to accomplish the job, for example a factory pattern wich holds an array of all your bitmaps. Like for instance create a class ImageFactory that holds a static ArrayList of bitmaps. Whenever you rotate your device the oncreateview and onactivitycreated methods are called again and this is the moment to check if you want to retrieve information from your factory or not. for example

if(savedInstanceState != null) { //we have a savedinstancestate so there probably are saved images ImageFactory.getBMPs(); }

hope you can do something with this info. good luck

Premonitory answered 12/4, 2013 at 7:36 Comment(1)
Thanks for your answer. So, what you are saying is that creating a class holder for those bitmaps, to save and restore them in onCreate inside my Fragment, would solve this problem ?Estradiol
L
2

this worked for me easy and simple inside your main activity

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

and inside your manifest

 <activity
            android:configChanges="orientation|screenSize"/>
Lewse answered 23/1, 2016 at 4:20 Comment(0)
P
1

I have created My Own Method for Save Fragment Data while Tab is Changed using SharedPreference:

Method like:

  public void saveData() {

    Log.d("msg", "Save Instance");
    SharedPreferences.Editor outState = getActivity().getSharedPreferences("order", Context.MODE_APPEND).edit();

    orderDate = orderDateEditText.getText().toString();
    orderBy = orderByEditText.getText().toString();
    orderMobileNo = orderMobileNoEditText.getText().toString();
    orderTransport = orderTransportEditText.getText().toString();
    orderInvoicePer = orderInvoicePerEditText.getText().toString(); 
    orderCharge = orderChargeEditText.getText().toString();
    orderGoodsDispatch = orderGoodsDispatchEditText.getText().toString();
    orderOthers = orderOthersEditText.getText().toString();
    orderIsDirect = orderIsDirectCheckBox.isChecked() ? 1 : 0;

    outState.putBoolean("saved", true);

    outState.putString("orderDate", orderDate);
    outState.putString("orderBy", orderBy);
    outState.putString("orderMobileNo", orderMobileNo);
    outState.putString("orderTransport", orderTransport);
    outState.putString("orderInvoicePer", orderInvoicePer);
    outState.putString("orderCharge", orderCharge);
    outState.putString("orderGoodsDispatch", orderGoodsDispatch);
    outState.putString("orderOthers", orderOthers);
    outState.putInt("orderIsDirect", orderIsDirect);

    outState.commit();
}

Call this Method when

    @Override
    public void onStop() {
    // TODO Auto-generated method stub
        super.onStop();

        saveData();
}

Replacing Saved Data with EditText:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onActivityCreated(savedInstanceState);

    /***** PUT DATA IN EDITTEXT is ITS AVAILABLE IN BUNDLE *****/
    SharedPreferences orderData = getActivity().getSharedPreferences("order", Context.MODE_APPEND);
    Log.d("msg", ""+orderData.getBoolean("saved", false));

    if(orderData.getBoolean("saved", false))
    {
        orderDate = orderData.getString("orderDate", "");
        orderBy = orderData.getString("orderBy", "");
        orderMobileNo = orderData.getString("orderMobileNo", "");
        orderTransport = orderData.getString("orderTransport", "");
        orderInvoicePer = orderData.getString("orderInvoicePer", ""); 
        orderCharge = orderData.getString("orderCharge", "");
        orderGoodsDispatch = orderData.getString("orderGoodsDispatch", "");
        orderOthers = orderData.getString("orderOthers", "");
        orderIsDirect = orderData.getInt("orderIsDirect", 0);

        orderDateEditText.setText(orderDate);
        orderByEditText.setText(orderBy);
        orderMobileNoEditText.setText(orderMobileNo);
        orderTransportEditText.setText(orderTransport);
        orderInvoicePerEditText.setText(orderInvoicePer);
        orderChargeEditText.setText(orderCharge);
        orderGoodsDispatchEditText.setText(orderGoodsDispatch);
        orderOthersEditText.setText(orderOthers);
        if(orderIsDirect == 0)
            orderIsDirectCheckBox.setChecked(false);
        else
            orderIsDirectCheckBox.setChecked(true);
    }
}

May this code is Helpful to you...

Thanks...

Pridemore answered 16/5, 2013 at 7:3 Comment(0)
F
1

I'm not quite sure if this question is still bothering you, since it has been several months. But I would like to share how I dealt with this. Here is the source code:

int FLAG = 0;
private View rootView;
private LinearLayout parentView;

/**
 * The fragment argument representing the section number for this fragment.
 */
private static final String ARG_SECTION_NUMBER = "section_number";

/**
 * Returns a new instance of this fragment for the given section number.
 */
public static Fragment2 newInstance(Bundle bundle) {
    Fragment2 fragment = new Fragment2();
    Bundle args = bundle;
    fragment.setArguments(args);
    return fragment;
}

public Fragment2() {

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    Log.e("onCreateView","onCreateView");
    if(FLAG!=12321){
        rootView = inflater.inflate(R.layout.fragment_create_new_album, container, false);
        changeFLAG(12321);
    }       
    parentView=new LinearLayout(getActivity());
    parentView.addView(rootView);

    return parentView;
}

/* (non-Javadoc)
 * @see android.support.v4.app.Fragment#onDestroy()
 */
@Override
public void onDestroy() {
    // TODO Auto-generated method stub
    super.onDestroy();
    Log.e("onDestroy","onDestroy");
}

/* (non-Javadoc)
 * @see android.support.v4.app.Fragment#onStart()
 */
@Override
public void onStart() {
    // TODO Auto-generated method stub
    super.onStart();
    Log.e("onstart","onstart");
}

/* (non-Javadoc)
 * @see android.support.v4.app.Fragment#onStop()
 */
@Override
public void onStop() {
    // TODO Auto-generated method stub
    super.onStop();
    if(false){
        Bundle savedInstance=getArguments();
        LinearLayout viewParent;

        viewParent= (LinearLayout) rootView.getParent();
        viewParent.removeView(rootView);

    }
    parentView.removeView(rootView);

    Log.e("onStop","onstop");
}
@Override
public void onPause() {
    super.onPause();
    Log.e("onpause","onpause");
}

@Override
public void onResume() {
    super.onResume();
    Log.e("onResume","onResume");
}

And here is the MainActivity:

/**
 * Fragment managing the behaviors, interactions and presentation of the
 * navigation drawer.
 */
private NavigationDrawerFragment mNavigationDrawerFragment;

/**
 * Used to store the last screen title. For use in
 * {@link #restoreActionBar()}.
 */

public static boolean fragment2InstanceExists=false;
public static Fragment2 fragment2=null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    setContentView(R.layout.activity_main);

    mNavigationDrawerFragment = (NavigationDrawerFragment) getSupportFragmentManager()
            .findFragmentById(R.id.navigation_drawer);
    mTitle = getTitle();

    // Set up the drawer.
    mNavigationDrawerFragment.setUp(R.id.navigation_drawer,
            (DrawerLayout) findViewById(R.id.drawer_layout));
}

@Override
public void onNavigationDrawerItemSelected(int position) {
    // update the main content by replacing fragments
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
    switch(position){
    case 0:
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.replace(R.id.container, Fragment1.newInstance(position+1)).commit();
        break;
    case 1:

        Bundle bundle=new Bundle();
        bundle.putInt("source_of_create",CommonMethods.CREATE_FROM_ACTIVITY);

        if(!fragment2InstanceExists){
            fragment2=Fragment2.newInstance(bundle);
            fragment2InstanceExists=true;
        }
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.replace(R.id.container, fragment2).commit();

        break;
    case 2:
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.replace(R.id.container, FolderExplorerFragment.newInstance(position+1)).commit();
        break;
    default: 
        break;
    }
}

The parentView is the keypoint. Normally, when onCreateView, we just use return rootView. But now, I add rootView to parentView, and then return parentView. To prevent "The specified child already has a parent. You must call removeView() on the ..." error, we need to call "parentView.removeView(rootView)", or the method I supplied is useless. I also would like to share how I found it. Firstly, I set up a boolean to indicate if the instance exists. When the instance exists, the rootView will not be inflated again. But then, logcat gave the child already has a parent thing, so I decided to use another parent as a intermediate Parent View. That's how it works. Hope it's helpful to you.

Fable answered 17/10, 2014 at 3:12 Comment(0)
S
0

Let me write my way to handle that problem. I keep an fragment object variable in the Activity, for later interaction. In onCreate() method I check if there is a savedInstanceState (caused by screen rotation or something else).

public class MainActivity extends AppCompatActivity {
PlaceAddFragment mFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if(savedInstanceState == null) {
        mFragment = new PlaceAddFragment();
        mFragment.setProfileImages(incomingPics);

        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.form_fragment_holder, mFragment, PlaceAddFragment.TAG)
                .commit();
    }else{
        mFragment = (PlaceAddFragment) getSupportFragmentManager().findFragmentByTag(PlaceAddFragment.TAG);
    }
}

In my fragment class I override onSaveInstanceState method and in it just add setRetainInstance(true), as shown below:

public class PlaceAddFragment extends Fragment {
public static final String TAG = ".PlaceAddFragment";
private ProfileImageAdapter mAdapter;
private List<ProfileImage> mProfileImages;
private Context mContext;

public PlaceAddFragment() {
    // Required empty public constructor
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    mContext = getContext();
    View view = inflater.inflate(R.layout.fragment_place_add, container, false);

    RecyclerView mRecyclerView = (RecyclerView) view.findViewById(R.id.hw_pictures_holder);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false));
    mAdapter = new ProfileImageAdapter(mContext, mProfileImages);
    mRecyclerView.setAdapter(mAdapter);

    return view;
}

@Override
public void onSaveInstanceState(final Bundle outState) {
    super.onSaveInstanceState(outState);
    setRetainInstance(true);
}

@Override
public void onDestroy() {
    super.onDestroy();
    mAdapter = null;
    mProfileImages = null;
}

public void setProfileImages(List<ProfileImage> incomingPics) {
    this.mProfileImages = incomingPics;
}

public List<ProfileImage> getProfileImages() {
    return this.mProfileImages;
}
}

If you want this code to work you should implement ProfileImageAdapter mAdapter, ProfileImage object and all xml layouts for those.

Sporogenesis answered 20/9, 2016 at 11:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.