Android custom composite view save and restore state not working

4.5k views Asked by At

Solved...

I have a composite view that contains some other controls. I am trying to override save onSaveInstanceState and onRestoreInstanceState, but am getting a strange result.

The Parcelable state argument to onRestoreInstanceState is not of my custom sub-class of BaseSavedState, SavedState and always seem to be BaseSavedState.EMPTY_STATE. (Look for the "always fails" code comment below...

It seems that the problem is likely with the save portion, as the SavedState.writeToParcel is not being called after onSaveInstanceState enters. Almost as if whoever is calling onSaveInstanceState is throwing the result away before persisting it to the Parcel.

If it makes a difference, this view is hosted inside a fragment.

Any ideas?

Here is my class definition:

public class AddressInput extends FrameLayout

Here is my onSaveInstanceState and onRestoreInstanceState pair:

@Override
protected Parcelable onSaveInstanceState()
{
    // Return saved state
    Parcelable superState = super.onSaveInstanceState();
    return new AddressInput.SavedState( superState, mCurrentLookUp );
}

@Override
protected void onRestoreInstanceState( Parcelable state )
{
    // **** (state == BaseSavedState.EMPTY_STATE) is also always true 

    // Cast state to saved state
    if ( state instance of AddressInput.SavedState )     // **** <---  always fails
    {
        AddressInput.SavedState restoreState = (AddressInput.SavedState)state;

        // Call super with its portion
        super.onRestoreInstanceState( restoreState.getSuperState() );

        // Get current lookup
        mCurrentLookUp = restoreState.getCurrentLookup();
    }
    else
        // Just send to super
        super.onRestoreInstanceState( state );
}

Here's my custom BaseSavedState sub-class (inner class of AddressInput) :

public static class SavedState extends BaseSavedState
{
    private String mCurrentLookup;

    public SavedState(Parcelable superState, String currentLookup)
    {
        super(superState);
        mCurrentLookup = currentLookup;
    }

    private SavedState(Parcel in)
    {
        super(in);
        this.mCurrentLookup = in.readString();
    }

    public String getCurrentLookup()
    {
        return mCurrentLookup;
    }

    @Override
    public void writeToParcel(Parcel out, int flags)
    {
        super.writeToParcel(out, flags);
        out.writeString( this.mCurrentLookup );
    }

    public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>()
    {
        public AddressInput.SavedState createFromParcel(Parcel in)
        {
            return new AddressInput.SavedState(in);
        }

        public AddressInput.SavedState[] newArray(int size) {
            return new AddressInput.SavedState[size];
        }
    };
}
2

There are 2 answers

0
DaveOddy On BEST ANSWER

Figured it out... My custom view had the same ID for its FrameLayout as was being used for the specific instance of the custom view. The state was being properly saved by the instance and then overwritten (cleared) by the FrameLayout which had no state to persist.

I also changed its base-class to a RelativeView which made more sense.

In custom view's XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:id="@+id/addressInput"
            android:background="@drawable/rounded_edit">

And the instance usage:

    <com.myStuff.AddressInput
            android:layout_height="wrap_content"
            android:layout_width="fill_parent"
            android:id="@+id/addressInput"
            android:layout_marginTop="10dp"
            app:addressMode="Domestic"
            app:showSelect="true"
            app:showClear="true" />

Changed to: (@+id/addressInput -> @+id/shippingAddress)

    <com.myStuff.AddressInput
            android:layout_height="wrap_content"
            android:layout_width="fill_parent"
            android:id="@+id/shippingAddress"
            android:layout_marginTop="10dp"
            app:addressMode="Domestic"
            app:showSelect="true"
            app:showClear="true" />

Makes you wish there was some scoping for IDs to prevent this kind of thing. Why should you have to know about a custom view's internals to make sure you avoid ID conflict?

0
mengfan On

It's glad to see you solved the problem.

For your question about custom composit view, I think an improvement could be made to the custom view's XML file:

use the "merge" tag, rather than any actual layout tag (for example: RelativeLayout) as the root element.

This will prevent the view redundancy, as described in this blog. Also you don't need to assign any id to the custom view, so the id conflict can be avoided.

Sorry I don't have enough reputation to add a comment, so I write another post.