
Preserving view and fragment state
It is important to save the state of a view, fragment, or activity. Each has a limited lifetime and may be stopped at any point, such as when the user closes the activity, navigates away from, or even rotates the device.
How to do it...
In order to preserve the state across certain actions, such as when the user switches between apps, we should save the state to the instance state parcel. Let's take a look at the following steps:
- Saving state for a view is done by inheriting from a type that implements the
IParcelable
interface, such asBaseSavedState
:private class InstanceState : BaseSavedState { public InstanceState(IParcelable superState) : base(superState) { } public InstanceState(Parcel parcel) : base(parcel) { Interval = parcel.ReadInt(); } public int Interval { get; set; } public override void WriteToParcel( Parcel dest, ParcelableWriteFlags flags) { base.WriteToParcel(dest, flags); dest.WriteInt(Interval); } }
- Now, Android needs a way to construct the instance state. This is achieved by providing an implementation of the
IParcelableCreator
instance:private class InstanceStateCreator: Java.Lang.Object, IParcelableCreator { public Java.Lang.Object CreateFromParcel( Parcel source) { return new InstanceState(source); } public Java.Lang.Object[] NewArray(int size) { return new InstanceState[size]; } }
- Finally, we need to connect
IParcelableCreator
withIParcelable
by creating a special method in theIParcelable
implementation:[ExportField("CREATOR")] private static InstanceStateCreator InitializeCreator() { return new InstanceStateCreator(); }
- Next, to actually save the data, we need to provide the data in the
OnSaveInstanceState()
method of the view:public override IParcelable OnSaveInstanceState() { IParcelable state = base.OnSaveInstanceState(); InstanceState instance = new InstanceState(state) { Interval = Interval }; return instance; }
- To start the restore process, we override the
OnRestoreInstanceState()
method and read the data out:public override void OnRestoreInstanceState( IParcelable state) { InstanceState instance = state as InstanceState; if (instance == null) { base.OnRestoreInstanceState(state); } else { base.OnRestoreInstanceState(instance.SuperState); Interval = instance.Interval; } }
Preserving state for a fragment or an activity is much easier and can be done simply by saving and restoring the values to the Bundle
object that is passed around. Let's take a look at the following steps:
- Saving state happens in the
OnSaveInstanceState()
method:public override void OnSaveInstanceState(Bundle outState) { base.OnSaveInstanceState(outState); outState.PutString("KEY", "VALUE"); }
- To restore state, we can make use of the
OnCreate()
method, and additionally (for fragments only), theOnCreateView()
method:public override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); if (savedInstanceState != null) { string value = savedInstanceState.GetString("KEY"); } }
Tip
The
Bundle
type may benull
inside theOnCreate()
andOnCreateView()
methods, so it needs to be checked first. - For activities only, there is also the
OnRestoreInstanceState()
method:protected override void OnRestoreInstanceState( Bundle savedInstanceState) { base.OnRestoreInstanceState(savedInstanceState); string value = savedInstanceState.GetString("KEY"); }
Tip
The OnRestoreInstanceState()
method of the activity is only called when there is a bundle
object to restore, so a null check isn't necessary.
How it works...
Views are frequently created and destroyed in Android, and often we have to save the current state of the view between these operations. One of the classic examples is the instance of the device rotation; the entire activity is destroyed along with the fragments and views. Android usually takes care of this for us, but sometimes, especially in the case of custom views, we need to do this ourselves.
Views store their state in a Parcel
object, and we can let Android know what to store in that Parcel
object by giving it an object that contains instructions on what to store and what to retrieve. This object is an instance of IParcelable
.
Instead of implementing all the bits from the IParcelable
interface, we'd rather inherit from BaseSavedSate
, which has most features already implemented, allowing us to only have to implement two members:
- The
WriteToParcel()
method, which allows us to write values (using a simple interface) to theParcel
object that will be persisted - The constructor which receives an instance of a
Parcel
object, which we can use to read the previously saved values.
When a view is reconstructed from the values in the Parcel
object, Android uses an instance of an IParcelableCreator
. This type contains members that allow Android to construct our IParcelable
instance, with which we can read the values that we had previously saved. There are two methods that we need to implement:
- The
CreateFromParcel()
method, which allows us to recreate ourIParcelable
instance from theParcel
object - The
NewArray()
method, which allows Android to create a collection of ourIParcelable
instance
Note
An instance of IParcelableCreator
must inherit from Java.Lang.Object
as the IParcelableCreator
interface inherits from the IJavaObject
interface.
We have to let Android know which creator to use, so we provide a field in the IParcelable
instance. Android has a specific name for this field, CREATOR
, and this is provided to Android by means of the Xamarin.Android exports helpers using the [ExportField]
attribute.
Once we have our IParcelable
instance, our IParcelableCreator
instance, and the special field, CREATOR
, we need to pass the state from our view into IParcelable
. This is done by simply creating an instance of IParcelable
. We then wrap the state from the base class, add any values we wish to save to our instance, and finally, pass it back to Android.
When Android tries to restore our view, it remembers which creator to use and constructs an instance of IParcelable
. We then read off the values from the Parcel
object into our view.
In order to read and write values, we create properties in IParcelable
that hold the values from the view or from the Parcel
object, depending on what direction the data is moving in.
Saving state for activities and fragments is much easier and only requires that we implement the OnSaveInstanceState()
method, in which we write values to Bundle
, a simple type of IParcelable
.
To restore state in an activity or fragment, there are many areas we access the Bundle
object, such as in the OnCreate()
, OnRestoreInstanceState()
, or OnCreateView()
methods.
There's more...
We can use the Bundle
type instead of creating the IParcelable
and IParcelalbleCreator
instances. In order to do so, we must be sure to save the state from the base into the bundle so that the base can restore its own state later on.
Although we don't have to create a custom type to store the view state, doing so makes it easier to manage and more flexible. In addition to possible future flexibility, we can abstract the state management a bit, keeping the view a bit cleaner.
See also
- Using custom views with layouts
- Creating and using fragments