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:

  1. Saving state for a view is done by inheriting from a type that implements the IParcelable interface, such as BaseSavedState:
    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);
        }
    }
  2. 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];
        }
      }
  3. Finally, we need to connect IParcelableCreator with IParcelable by creating a special method in the IParcelable implementation:
    [ExportField("CREATOR")]
    private static InstanceStateCreator InitializeCreator() {
      return new InstanceStateCreator();
    }
  4. 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;
    }
  5. 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:

  1. Saving state happens in the OnSaveInstanceState() method:
    public override void OnSaveInstanceState(Bundle outState) {
      base.OnSaveInstanceState(outState);
      outState.PutString("KEY", "VALUE");
    }
  2. To restore state, we can make use of the OnCreate() method, and additionally (for fragments only), the OnCreateView() method:
    public override void OnCreate(Bundle savedInstanceState) {
      base.OnCreate(savedInstanceState);
      if (savedInstanceState != null) {
        string value = savedInstanceState.GetString("KEY");
      }
    }

    Tip

    The Bundle type may be null inside the OnCreate() and OnCreateView() methods, so it needs to be checked first.

  3. 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 the Parcel 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 our IParcelable instance from the Parcel object
  • The NewArray() method, which allows Android to create a collection of our IParcelable 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
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset