I am working on an Android project, and I want to pass a custom class MainActivityModel to a Fragment, MainActivityPlaceholderFragment.
I have made MainActivityModel serializable:
public class MainActivityModel implements Serializable{
public int current = 0;
public int pageCount = 0;
public boolean pristine = true;
// Stores the fetched dataMap
public ArrayList<HashMap<String, String>> arrayList;
public MainActivityModel() {
this.arrayList = new ArrayList<>();
}
public String getCategory() {
return Util.categories[current];
}
public CharSequence getmTitle () {
return Util.toTitleCase(
Util.mapCategoryPretty(Util.categories[current]));
}
}
and I am passing it to the Fragment like this:
public static MainActivityPlaceholderFragment newInstance(MainActivityModel mainActivityModel) {
MainActivityPlaceholderFragment fragment = new MainActivityPlaceholderFragment();
Bundle args = new Bundle();
args.putSerializable(ARG_DATA_MODEL, mainActivityModel);
fragment.setArguments(args);
Log.v(LOG_TAG, "Created: " + mainActivityModel.getmTitle());
return fragment;
}
I access it like this:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mainActivityModel = (MainActivityModel) getArguments().getSerializable(ARG_DATA_MODEL);
mMainActivityPlaceholderFragmentView = new MainActivityPlaceholderFragmentView(this, mainActivityModel);
mCallbacks.onPlaceholderFragmentCreated(mainActivityModel.current);
}
I initially thought (after reading the answers I mention below), that serialization converts data to bytes and restores them subsequently when needed. So my object would be copied. This is ok if I only want to access the data. But I also wanted to modify the actual model (which is referenced in MainActivity) from the fragment.
To experiment, I set pristine to false in the Fragment, and logging that in MainActivity, it was indeed false.
But if serializable is pass-by-value, how is this happening?
What I've read:
A reference to a
Serializableobject is still an object reference, it's no different from passing aListobject or aFooobject. The confusing part is if and where the serialization takes place.From the documentation of
android.app.Fragment.setArguments(Bundle):There are two ways to achieve this:
Bundleonly store raw bytes, and serialize/deserialize for everyget/putoperation.Bundleto hold live objects, and ask it to serialize/deserialize everything when the fragment needs to be destroyed/recreated.Clearly, the first option is very inefficient:
get/putoperations are much more frequent than activity/fragment life cycle changes. Therefore, Android will only serialize/deserialize when needed on life cycle changes.This causes the "weird" behavior in your use case. You assumed that your
Serializableobject is serialized immediately byBundle, where instead theBundlesimply holds a reference to your object. Since the fragment is not destroyed between thenewInstanceandonCreatecall, you are seeing the exact sameBundleholding the exact same references.Of course, you should not rely on these references to stay intact. Any time your application is asked to persist its state (e.g. when going to the background, when rotating the screen, or when the system needs to free up RAM), those objects are serialized and the references are gone. The objects will be re-created from the serialized data, but they will have different references.