0

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:

  1. What is object serialization?
  2. R: Pass by reference
  3. what is Serializable in Java
  4. What is serialization in Java?
xyz
  • 3,349
  • 1
  • 23
  • 29
  • It doesn't matter if it is serializable or not ... the point that bundle can't handle references ... it always serialize/deserialize(parcel/unparcel) ... just think about such case: Activity with some Intent goes background and even get killed the you return to it ... gues why it get the intent back ? it is not references ... system just save it to the parcel and recreate if needed – Selvin Jun 19 '15 at 10:28
  • That's my question. If it's copying the object, how are the changes getting reflected back in `MainActivity`? The changes should stay limited to the fragment. – xyz Jun 19 '15 at 10:29
  • @prakharsingh95 What do you understand by copying the Object ? – Ankur Anand Jun 19 '15 at 10:30
  • It is not the same object ... changes made in the original will not apply in the object passed through bundle(I mean the one back "on the other side") ... – Selvin Jun 19 '15 at 10:30
  • @AnkurAnand it's converting the object to bytes, and when I `get` them from the `Bundle` again, it converts the serialized representation to object. – xyz Jun 19 '15 at 10:31
  • **`MainActivityModel` returned from `getArguments().getSerializable(ARG_DATA_MODEL)` will always have the state as same as at the time when you passed it to `MainActivityPlaceholderFragment .newInstance()` (well mabe at the time when you added it to the FragmentManager - but it doesn't matter, because I'm pretty sure that you are doing it right after)** – Selvin Jun 19 '15 at 10:37
  • @Selvin yes, but I checked may times before asking. I am passing it to the fragment, modifying it in fragment and then logging it in mainactivity using a callback (callback doesn't pass anything). The changes are retained! – xyz Jun 19 '15 at 10:40
  • ok now change orientation ... and try again ... – Selvin Jun 19 '15 at 10:41
  • changing orientation recreates activity. I have not implemented configuration changes yet. – xyz Jun 19 '15 at 11:00

4 Answers4

3

A reference to a Serializable object is still an object reference, it's no different from passing a List object or a Foo object. The confusing part is if and where the serialization takes place.

From the documentation of android.app.Fragment.setArguments(Bundle):

The arguments supplied here will be retained across fragment destroy and creation.

There are two ways to achieve this:

  • Make Bundle only store raw bytes, and serialize/deserialize for every get/put operation.
  • Allow 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 by Bundle, where instead the Bundle simply holds a reference to your object. Since the fragment is not destroyed between the newInstance and onCreate call, you are seeing the exact same Bundle 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.

Selvin
  • 6,598
  • 3
  • 37
  • 43
Mattias Buelens
  • 19,609
  • 4
  • 45
  • 51
  • 1
    sorry, i had to ... **not rely** is the most important here ... I'm pretty sure that it is some kind of optimalization ... and may not works on some API level with "original" Fragment – Selvin Jun 19 '15 at 10:46
  • Ok it Android can do that, why can't andoid simply allow me to store a reference in the bundle. My object's lifetime is same as that of the parent activity. – xyz Jun 19 '15 at 10:55
  • As noted here (http://stackoverflow.com/a/14917265/3887393), it's not possible to store an un-marshallable object in a bundle – xyz Jun 19 '15 at 10:58
  • @prakharsingh95 *"why can't andoid simply allow me to store a reference in the bundle. My object's lifetime is same as that of the parent activity."* Because your parent activity can *also* be destroyed/recreated. For example, if you rotate the screen with your parent activity opened, the whole activity (and its fragments) may need to be destroyed and recreated to use a different activity layout file. – Mattias Buelens Jun 19 '15 at 11:27
  • @MattiasBuelens yes. If my parent activity gets destroyed, then on recreation everything including the fragments will be recreated. I mean, I if can guarantee that the object will be older than the fragment and will always outlive it, what's the issue? – xyz Jun 19 '15 at 11:29
  • @prakharsingh95 *"it's not possible to store an un-marshallable object in a bundle."* Correct, it is impossible to store something that *cannot be serialized to / deserialized from raw bytes*. That doesn't mean that it *must always be (de)serialized* on every use. – Mattias Buelens Jun 19 '15 at 11:30
  • @prakharsingh95 *"if I can guarantee that the object will be older than the fragment and will always outlive it, what's the issue?"* A `Bundle` has no idea whether your original object will still be alive when it is later deserialized, and it would have no way to find your original object later on. You could try to let some other entity (e.g. a `Service`) manage your object's life cycle, and only store a (serializable) "key" to that object so it can be retrieved later from the same service, but then it's *your* responsibility of making sure that "managing entity" stays alive. – Mattias Buelens Jun 19 '15 at 11:41
  • @MattiasBuelens Thank you. Indeed, I plan to shift all my *"unsafe"* variables to the **arguments Bundle**. And I would like to add that I think my doubt was a valid one. People bit my head off. – xyz Jun 19 '15 at 11:54
0

In Java all objects are passed by reference, only primitive types (int,float,long...) are passed by value.

I doesn't really know how Serializable works, but if its converting to byte[] that isn't a primitive type so that's why it is working.

If you write a Serializable class to a file and open it as ASCII you would see kind of a recursive toString() followed by what could probably be the data as bytes.

Hope this helps.

Nanoc
  • 2,381
  • 1
  • 20
  • 35
0

Serialization creates deep copies of your objects, meaning that if you serialize then deserialize an object containing other objects, you will get new independent objects (with new references), copies of everything, with absolutely no reference to the objects you still had on the heap. Thus, if you modify the objects you have just deserialized, you will only modify these objects, not any previous reference you could have on the heap.

If you want to reconciliate references you have just deserialized with your objects in memory, you have to code it.

  • "If you want to reconciliate references you have just deserialized with your objects in memory, you have to code it." Yes. I was worried because I didn't do this and it's still working. I want to understand why. I have also added how I'm verifying this behaviour in comments to the question. – xyz Jun 19 '15 at 10:41
  • It could be because of java String behavior. Strings have a specific allocation system that avoid dupes. There is an internal pool of string and if you create a new string object, it will look into the pool if your new string already exists. If it exists, you will get a reference to the existing string. As strings are immutable, that is no issue. – Loïc Lacombe Jun 19 '15 at 10:46
0

The behavior you are describing looks a lot like Parcel active objects, not sun-java-style serialization.