If you want to pass values between activities in Android, you’ll probably want to implement Parcelable sooner or later. This isn’t particularly slick even when writing natively, but add Mono in and it becomes even murkier. Fortunately .NET’s Generics support helps us out a little. The reason for the rather heavy implementation is intents can cross process boundaries, meaning you can’t just provide a memory address. This gives you the ability to call apps / resources you haven’t programmed yourself, but unfortunately it means inter-activity communication is ‘heavy’ for want of a better term. If you’re thinking of using IParcelable in a brand new app that isn’t going to be receiving 3rd party parcels, consider the ‘alternatives’ at the end of this guide first. IParcelable is somewhat of a convenience API for serialising objects to binary and then de-serialising. You might be better off doing this yourself rather than using the ‘Android’ method.
Our Class
For this demo I want to pass an array of a really simple class:
public sealed class SelectListItem { // Convenience constructors public SelectListItem() {} public SelectListItem(int id, string name) { Id = id; Name = name; } // The actual properties for this class public int Id { get; set; } public string Name { get; set; } }
Reference ‘Mono.Android.Export’
Before we start, make sure to reference the ‘Mono.Android.Export’ assembly:
Implementing IParcelable
This class will be used to populate a ListActivity with a series of items that can be selected. First we need to inherit from Java.Lang.Object, and implement the IParcelable interface:
public sealed class SelectListItem : Java.Lang.Object, IParcelable
So why do we have to inherit from Java.Lang.Object? The Android runtime needs to be able to call the methods that we define as part of IParcelable, to do this our class must have a dual-personality. One half of the class is .NET, the other half is Java, it is this Java side that Android can see. When a method is called on the Java side, it will execute the .NET code. Clever huh? Unfortunately this means we now have two objects instead of one in memory, it also means that Mono must keep track of both objects. To do this it uses a global ID or reference, this reference links the two instances together. Unfortunately GREFs, are a very, very limited resource. The emulator only supports ~2,000 and devices usually only support ~50,000 GREFs. For more information on GREFs, check the Architecture and Garbage Collection documents over at Xamarin.
Before we implement the interface, we need to add an additional constructor that can accept a Parcel type. This constructor will populate the instance with the values from the parcel – the parcel being the ‘wrapped’ data of an instance of the same type:
// Create a new SelectListItem populated with the values in parcel private SelectListItem(Parcel parcel) { Id = parcel.ReadInt(); Name = parcel.ReadString(); }
Note the order we read in the values, this is very important and must mirror the order in WriteToParcel below.
Next we need to implement the interface:
public int DescribeContents() { return 0; } // Save this instance's values to the parcel public void WriteToParcel(Parcel dest, ParcelableWriteFlags flags) { dest.WriteInt(Id); dest.WriteString(Name); }
DescribeContents is used for flagging ‘special objects’ within the parcel. I’ve never had cause to use this field, and 0 means ‘no special objects’.
WriteToParcel is where we write the instance data to the parcel, pay special attention to the order you write the values. This same order must be mirrored when reading in the parcel above. The flags attribute gives additional information on how the data should be written, it’s unlikely you’ll need to check these flags.
The CREATOR field
All Parcelable objects must have a public static field called CREATOR. The instance behind this field is responsible for creating instances of the object and populating them from the parcel. This can be done in two ways, an internal class that is implementation-specific, or a generic class that can be re-used repeatedly. The first method is the method commonly used in Java applications. However, with .NET we gain a much more robust Generics implementation so we’ll go down that route. However if you’re interested in the Java-favoured implementation it’s available in the sample project as a comment.
Without further ado, let’s look at how to declare our creator:
// The creator creates an instance of the specified object private static readonly GenericParcelableCreator<SelectListItem> _creator = new GenericParcelableCreator<SelectListItem>((parcel) => new SelectListItem(parcel)); [ExportField("CREATOR")] public static GenericParcelableCreator<SelectListItem> GetCreator() { return _creator; }
First we declare a private static instance, it’s required to be static by the Android API. Because this instance is static, it’s critical that your creator is thread safe otherwise bad things will happen. After the actual instance declaration we create a public static method, and decorate it with the ExportField attribute. Unfortunately this attribute can only be used on methods, so we can’t decorate our field directly. ExportField will ensure the Java version of our class gains a public static field named CREATOR.
A Generic ParcelableCreator Implementation
Earlier I mentioned that when developing with Java, each Parcelable requires a custom creator. Fortunately with .NET we gain a robust Generic implementation and we can use a generic creator that we can use again and again:
public sealed class GenericParcelableCreator<T> : Java.Lang.Object, IParcelableCreator where T : Java.Lang.Object, new() { private readonly Func<Parcel, T> _createFunc; /// <summary> /// Initializes a new instance of the <see cref="ParcelableDemo.GenericParcelableCreator`1"/> class. /// </summary> /// <param name='createFromParcelFunc'> /// Func that creates an instance of T, populated with the values from the parcel parameter /// </param> public GenericParcelableCreator(Func<Parcel, T> createFromParcelFunc) { _createFunc = createFromParcelFunc; } #region IParcelableCreator Implementation public Java.Lang.Object CreateFromParcel(Parcel source) { return _createFunc(source); } public Java.Lang.Object[] NewArray(int size) { return new T[size]; } #endregion }
The creator must also be usable directly by Android, so again we have to inherit from Java.Lang.Object. We add two restrictions to the generic type parameter, first it must inherit from Java.Lang.Object (as all Parcelables must), and second it must have an empty constructor. This allows us to create an array of instances of the type, as required by IParcelableCreator.
Next in the constructor we require a delegate that can create and populate an instance of T. If you check back at our instantiation of the creator, we use a lambda expression that instantiates the instance using the private constructor we created that accepts a Parcel. Finally we implement the two methods required by IParcelableCreator, these simply create and populate the type, or create an array of the type.
Also remember that the creator must be thread-safe, if it isn’t bad things happen. To ensure thread safety we’ve made the state immutable, immutable types are by definition thread safe. If you need to create your own creator, be careful to follow thread safety guidelines. Microsoft have a good book on patterns and thread safety you can download for free.
Sample Code
You can download the sample project from GitHub here.
Alternative to IParcelable
The problem with Parcelables is that you’re using up your very limited GREF resource. Additionally running two instances of an object across two garbage collectors isn’t the best use of memory or CPU resources. You’ll actually double up on instances as well, the instances that get packaged up in a parcel, and the instances that get extracted. The first set (senders) you can often clear from memory quickly, however the second set (received values) could be memory resident for quite some time as they’re used by the activity. The alternative is to either send just enough information for the activity to generate its own data, or you can send the data in an alternative format. You can send byte arrays, so you can use ISerializable and serialise your objects to binary. The other option is to use JSON (or other text-based format) and pass JSON strings between activities instead.
Unfortunately there is no ‘best’ solution, only the solution that meets your specific needs best.