10

In my app, I have Fragment which is inside ViewPager. Fragment contains RecyclerView with list of data fetched from web api based on user selection.

On my Fragment onSaveInstanceState I save list data to Bunde, to keep the data on configuration changes etc.

public void onSaveInstanceState(Bundle savedState) {
    super.onSaveInstanceState(savedState);
    savedState.putParcelableArrayList(LIST_STORAGE_KEY, new ArrayList<>(mItemAdapter.getModels()));
}

Now I have started to see TransactionTooLargeException on my app error reporting.

It seems that in some cases the list which Im putting to Bundle, is too large (as it is collection of quite complex objects).

How should I handle this case? How to store (and restore) my Fragment state.

Is it ok to use setRetainInstance(true) on Fragments inside ViewPager?

devha
  • 3,307
  • 4
  • 28
  • 52
  • If i did it, I'd move all network operations & data, retrieved from network storing to some service. In this case you don't need to save it, since it is stored in the service – Vladyslav Matviienko Jan 31 '17 at 09:05
  • This sound like little overkill for now. Easier solution would be to fetch data from web api again, but i think it is not very good user experience. – devha Jan 31 '17 at 10:31
  • well, as an ugly and fast alternative, you could save the data to shared preferences, or to a file. – Vladyslav Matviienko Jan 31 '17 at 10:40
  • 1
    Have you seen this: http://blog.sqisland.com/2016/09/transactiontoolargeexception-crashes-nougat.html If you have a large piece of data I'd recommend serializing it to the file system, not making the network request again (if the data is the same), and pass a reference (name of the file, etc) in the bundle. You can do that lots of ways. – Charlie Collins Feb 03 '17 at 16:39
  • see this link,http://stackoverflow.com/questions/39098590/android-os-transactiontoolargeexception-on-nougat – RejoylinLokeshwaran Feb 06 '17 at 10:03
  • You can try to save it in local storage like preference, file or SQLite – Lalit Jadav Feb 09 '17 at 12:33

3 Answers3

8

To preserve big chunks of data, Google is suggesting to do it with Fragment that retain instance. Idea is to create empty Fragment without view with all necessary fields, that would otherwise been saved in Bundle. Add setRetainInstance(true); to Fragment's onCreate method. And than save data in Fragment on Activity's onDestroy and load them onCreate. Here is and example of Activity:

public class MyActivity extends Activity {

private DataFragment dataFragment;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // find the retained fragment on activity restarts
    FragmentManager fm = getFragmentManager();
    dataFragment = (DataFragment) fm.findFragmentByTag("data");

    // create the fragment and data the first time
    if (dataFragment == null) {
        // add the fragment
        dataFragment = new DataFragment();
        fm.beginTransaction().add(dataFragment, "data").commit();
        // load the data from the web
        dataFragment.setData(loadMyData());
    }

    // the data is available in dataFragment.getData()
    ...
}

@Override
public void onDestroy() {
    super.onDestroy();
    // store the data in the fragment
    dataFragment.setData(collectMyLoadedData());
}
}

And example of Fragment:

public class DataFragment extends Fragment {

// data object we want to retain
private MyDataObject data;

// this method is only called once for this fragment
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // retain this fragment
    setRetainInstance(true);
}

public void setData(MyDataObject data) {
    this.data = data;
}

public MyDataObject getData() {
    return data;
}
}
svkaka
  • 3,942
  • 2
  • 31
  • 55
Shobhit
  • 1,096
  • 11
  • 30
  • This is indeed recommended by google, but does this work inside fragment, using DataFragment as nested fragment? Currently my fragment works as independent component and it would be nice if the fragment can save/restore it state whit out giving that task to parent activity. – devha Feb 05 '17 at 21:11
  • for me creating an empty fragment worked because I was exchanging huge data (>1MB) between services and application. – Shobhit Feb 06 '17 at 04:01
  • 2
    @devha It can be done as a nested fragment. Add this before 'beginTransaction...' `dataFragment.setTargetFragment(this, 0);` – Gary99 Feb 09 '17 at 01:00
  • could you point out where does google recommends the way you suggested? (cc: @devha) – musooff Jan 18 '21 at 11:14
2

If you don't want your fragment to use setRetainInstance(true), then you can add an empty fragment with setRetainInstance(true) to your activity. This is useful since child fragments cannot use setRetainInstance(true).

Example:

public class BaseActivity extends Activity {

  RetainedFragment retainedFragment;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    retainedFragment = (RetainedFragment) getFragmentManager().findFragmentByTag("retained_fragment");
    if (retainedFragment == null) {
      retainedFragment = new RetainedFragment();
      getFragmentManager().beginTransaction().add(retainedFragment, "retained_fragment").commit();
    }
  }

  public <T> T getState(String key) {
    //noinspection unchecked
    return (T) retainedFragment.map.get(key);
  }

  public void saveState(String key, Object value) {
    retainedFragment.map.put(key, value);
  }

  public boolean has(String key) {
    return retainedFragment.map.containsKey(key);
  }

  public static class RetainedFragment extends Fragment {

    HashMap<String, Object> map = new HashMap<>();

    @Override public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setRetainInstance(true);
    }

  }

}

Then, in your fragment, you can cast getActivity() to your Activity class and use saveState(String, Object) and getState(String) to save your list.


There are other discussions on this which can be found at the following locations:

What to do on TransactionTooLargeException

android.os.TransactionTooLargeException on Nougat (Accepted answer suggests setRetainInstance(true)).

Community
  • 1
  • 1
Jared Rummler
  • 37,824
  • 19
  • 133
  • 148
0

setRetainInstance() is the best way to achieve that without side effects. Using static will cause memory leak and there is no use of saving the state in onSaveInstanceState() and getting it back since, setRetainInstance() does that for you.

So create a field for the list in fragment class and always check for null or size of list to begin operation of fetching latest data

arjun
  • 3,514
  • 4
  • 27
  • 48