How to use Parcel in Android?

AndroidParcel

Android Problem Overview


I'm trying to use Parcel to write and then read back a Parcelable. For some reason, when I read the object back from the file, it's coming back as null.

public void testFoo() {
    final Foo orig = new Foo("blah blah");

    // Wrote orig to a parcel and then byte array
    final Parcel p1 = Parcel.obtain();
    p1.writeValue(orig);
    final byte[] bytes = p1.marshall();


    // Check to make sure that the byte array seems to contain a Parcelable
    assertEquals(4, bytes[0]); // Parcel.VAL_PARCELABLE


    // Unmarshall a Foo from that byte array
    final Parcel p2 = Parcel.obtain();
    p2.unmarshall(bytes, 0, bytes.length);
    final Foo result = (Foo) p2.readValue(Foo.class.getClassLoader());


    assertNotNull(result); // FAIL
    assertEquals( orig.str, result.str );
}


protected static class Foo implements Parcelable {
    protected static final Parcelable.Creator<Foo> CREATOR = new Parcelable.Creator<Foo>() {
        public Foo createFromParcel(Parcel source) {
            final Foo f = new Foo();
            f.str = (String) source.readValue(Foo.class.getClassLoader());
            return f;
        }

        public Foo[] newArray(int size) {
            throw new UnsupportedOperationException();
        }

    };


    public String str;

    public Foo() {
    }

    public Foo( String s ) {
        str = s;
    }

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int ignored) {
        dest.writeValue(str);
    }


}

What am I missing?

UPDATE: To simplify the test I've removed the reading and writing of files in my original example.

Android Solutions


Solution 1 - Android

Ah, I finally found the problem. There were two in fact.

  1. CREATOR must be public, not protected. But more importantly,
  2. You must call setDataPosition(0) after unmarshalling your data.

Here is the revised, working code:

public void testFoo() {
    final Foo orig = new Foo("blah blah");
    final Parcel p1 = Parcel.obtain();
    final Parcel p2 = Parcel.obtain();
    final byte[] bytes;
    final Foo result;

    try {
        p1.writeValue(orig);
        bytes = p1.marshall();

        // Check to make sure that the byte stream seems to contain a Parcelable
        assertEquals(4, bytes[0]); // Parcel.VAL_PARCELABLE

        p2.unmarshall(bytes, 0, bytes.length);
        p2.setDataPosition(0);
        result = (Foo) p2.readValue(Foo.class.getClassLoader());
    
    } finally {
        p1.recycle();
        p2.recycle();
    }


    assertNotNull(result);
    assertEquals( orig.str, result.str );

}

protected static class Foo implements Parcelable {
    public static final Parcelable.Creator<Foo> CREATOR = new Parcelable.Creator<Foo>() {
        public Foo createFromParcel(Parcel source) {
            final Foo f = new Foo();
            f.str = (String) source.readValue(Foo.class.getClassLoader());
            return f;
        }

        public Foo[] newArray(int size) {
            throw new UnsupportedOperationException();
        }

    };


    public String str;

    public Foo() {
    }

    public Foo( String s ) {
        str = s;
    }

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int ignored) {
        dest.writeValue(str);
    }


}

Solution 2 - Android

Beware! Dont use Parcel for serialization to a file

> Parcel is not a general-purpose serialization mechanism. This class (and the corresponding Parcelable API for placing arbitrary objects into a Parcel) is designed as a high-performance IPC transport. As such, it is not appropriate to place any Parcel data in to persistent storage: changes in the underlying implementation of any of the data in the Parcel can render older data unreadable.

from http://developer.android.com/reference/android/os/Parcel.html

Solution 3 - Android

I find that Parcelable is most often used in Android within data Bundles, but more specifically within a Handler that is sending and receiving messages. As an example, you might have an AsyncTask or a Runnable that needs to run in the background but post resulting data to the Main thread or Activity.

Here's a simple example. If I have a Runnable that looks like this:

package com.example;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.example.data.ProductInfo;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.squareup.okhttp.OkHttpClient;

public class AsyncRunnableExample extends Thread {
	public static final String KEY = "AsyncRunnableExample_MSG_KEY";
	
	private static final String TAG = AsyncRunnableExample.class.getSimpleName();
	private static final TypeToken<ProductInfo> PRODUCTINFO =
		      new TypeToken<ProductInfo>() {
		      };
	private static final Gson GSON = new Gson();
		      
	private String productCode;
	OkHttpClient client;
	Handler handler;
	
	public AsyncRunnableExample(Handler handler, String productCode)
	{
		this.handler = handler;
		this.productCode = productCode;
		client = new OkHttpClient();
	}
	
	@Override
	public void run() {
		String url = "http://someserver/api/" + productCode;

		try
		{
			HttpURLConnection connection = client.open(new URL(url));
		    InputStream is = connection.getInputStream();
		    InputStreamReader isr = new InputStreamReader(is);

		    // Deserialize HTTP response to concrete type.
		    ProductInfo info = GSON.fromJson(isr, PRODUCTINFO.getType());

		    Message msg = new Message();
		    Bundle b = new Bundle();
		    b.putParcelable(KEY, info);
		    msg.setData(b);
		    handler.sendMessage(msg);

		}
		catch (Exception err)
		{
			Log.e(TAG, err.toString());
		}

	}
}

As you can see, this runnable takes a Handler in its constructor. This is called from some Activity like this:

static class MyInnerHandler extends Handler{
        WeakReference<MainActivity> mActivity;
        
        MyInnerHandler(MainActivity activity) {
        	mActivity = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
        	MainActivity theActivity = mActivity.get();
			ProductInfo info = (ProductInfo) msg.getData().getParcelable(AsyncRunnableExample.KEY);
        	
			// use the data from the Parcelable 'ProductInfo' class here

			}
        }
    }
    private MyInnerHandler myHandler = new MyInnerHandler(this);

    @Override
	public void onClick(View v) {
		AsyncRunnableExample thread = new AsyncRunnableExample(myHandler, barcode.getText().toString());
		thread.start();
	}

Now, all that is left is the heart of this question, how you define a class as Parcelable. I've chosen a fairly complex class to show because there are some things you would not see with a simple one. Here is the ProductInfo class, which Parcels and unParcels cleanly:

public class ProductInfo implements Parcelable {

	private String brand;
	private Long id;
	private String name;
	private String description;
	private String slug;
	private String layout; 
	private String large_image_url;
	private String render_image_url;
	private String small_image_url;
	private Double price;
	private String public_url;
	private ArrayList<ImageGroup> images;
	private ArrayList<ProductInfo> related;
	private Double saleprice;
	private String sizes;
	private String colours;
	private String header;
	private String footer;
	private Long productcode;
	
	// getters and setters omitted here

	@Override
	public void writeToParcel(Parcel dest, int flags) {
		dest.writeLong(id);
		dest.writeString(name);
		dest.writeString(description);
		dest.writeString(slug);
		dest.writeString(layout);
		dest.writeString(large_image_url);
		dest.writeString(render_image_url);
		dest.writeString(small_image_url);
		dest.writeDouble(price);
		dest.writeString(public_url);
		dest.writeParcelableArray((ImageGroup[])images.toArray(), flags);
		dest.writeParcelableArray((ProductInfo[])related.toArray(), flags);
		dest.writeDouble(saleprice);
		dest.writeString(sizes);
		dest.writeString(colours);
		dest.writeString(header);
		dest.writeString(footer);
		dest.writeLong(productcode);
	}
	
	public ProductInfo(Parcel in)
	{
		id = in.readLong();
		name = in.readString();
		description = in.readString();
		slug = in.readString();
		layout = in.readString();
		large_image_url = in.readString();
		render_image_url = in.readString();
		small_image_url = in.readString();
		price = in.readDouble();
		public_url = in.readString();
		images = in.readArrayList(ImageGroup.class.getClassLoader());
		related = in.readArrayList(ProductInfo.class.getClassLoader());
		saleprice = in.readDouble();
		sizes = in.readString();
		colours = in.readString();
		header = in.readString();
		footer = in.readString();
		productcode = in.readLong();
	}
	
	public static final Parcelable.Creator<ProductInfo> CREATOR = new Parcelable.Creator<ProductInfo>() {
        public ProductInfo createFromParcel(Parcel in) {
            return new ProductInfo(in); 
        }

        public ProductInfo[] newArray(int size) {
            return new ProductInfo[size];
        }
    };

	@Override
	public int describeContents() {
		return 0;
	}
}

The CREATOR is critical, as is the resulting constructor taking a Parcel. I included the more complex data types so you could see how to Parcel and unParcel Arrays of Parcelable objects. This is a common thing when using Gson to convert JSON into objects with children as in this example.

Solution 4 - Android

To get a better understanding of the Parcel concept Try the below Link

> http://prasanta-paul.blogspot.com/2010/06/android-parcelable-example.html

hope this helps :)

Solution 5 - Android

I too had similar problem. only the following snippet from emmby and this helped me out.

    public static final Parcelable.Creator<Foo> CREATOR = new Parcelable.Creator<Foo>() {
        public Foo createFromParcel(Parcel source) {
            final Foo f = new Foo();
            f.str = (String) source.readValue(Foo.class.getClassLoader());
            return f;
        }

        public Foo[] newArray(int size) {
            throw new UnsupportedOperationException();
        }

It should be kept in each of the class that implements Parcelable

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionemmbyView Question on Stackoverflow
Solution 1 - AndroidemmbyView Answer on Stackoverflow
Solution 2 - AndroidCarl D'HalluinView Answer on Stackoverflow
Solution 3 - AndroidDavid S.View Answer on Stackoverflow
Solution 4 - AndroidkAnNaNView Answer on Stackoverflow
Solution 5 - AndroidA_rmasView Answer on Stackoverflow