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
Serializable
object is still an object reference, it's no different from passing aList
object or aFoo
object. 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:
Bundle
only store raw bytes, and serialize/deserialize for everyget
/put
operation.Bundle
to 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
/put
operations 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
Serializable
object is serialized immediately byBundle
, where instead theBundle
simply holds a reference to your object. Since the fragment is not destroyed between thenewInstance
andonCreate
call, you are seeing the exact sameBundle
holding 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.