269

I've successfully implemented onRetainNonConfigurationInstance() for my main Activity to save and restore certain critical components across screen orientation changes.

But it seems, my custom views are being re-created from scratch when the orientation changes. This makes sense, although in my case it's inconvenient because the custom view in question is an X/Y plot and the plotted points are stored in the custom view.

Is there a crafty way to implement something similar to onRetainNonConfigurationInstance() for a custom view, or do I need to just implement methods in the custom view which allow me to get and set its "state"?

JJD
  • 50,076
  • 60
  • 203
  • 339
Brad Hein
  • 10,997
  • 12
  • 51
  • 74

10 Answers10

481

I think this is a much simpler version. Bundle is a built-in type which implements Parcelable

public class CustomView extends View
{
  private int stuff; // stuff

  @Override
  public Parcelable onSaveInstanceState()
  {
    Bundle bundle = new Bundle();
    bundle.putParcelable("superState", super.onSaveInstanceState());
    bundle.putInt("stuff", this.stuff); // ... save stuff 
    return bundle;
  }

  @Override
  public void onRestoreInstanceState(Parcelable state)
  {
    if (state instanceof Bundle) // implicit null check
    {
      Bundle bundle = (Bundle) state;
      this.stuff = bundle.getInt("stuff"); // ... load stuff
      state = bundle.getParcelable("superState");
    }
    super.onRestoreInstanceState(state);
  }
}
Kobor42
  • 5,129
  • 1
  • 17
  • 22
  • This was exactly what I needed to find. I had everything set up for saving using bundles, but I was getting an exception. The missing piece (that I got from you) was that I needed to return the same parcelable to the super that I saved from the super into my custom bundle. – David Hay Jan 13 '12 at 06:03
  • I'd still move that state variable into it's own static inner class to keep your state variables away from your class vars. But yes Bundle is much simpler than Parcelable – Blundell May 11 '12 at 13:09
  • 5
    Why *wouldn't* `onRestoreInstanceState` be called with a Bundle if `onSaveInstanceState` returned a Bundle? – Qwertie Jun 12 '12 at 22:59
  • 5
    `OnRestoreInstance` is inherited. We can't change the header. `Parcelable` is just an interface, `Bundle` is an implementation for that. – Kobor42 Aug 09 '12 at 07:35
  • Also note that the default implementation in `View` returns null, so if you are implementing an entirely custom view you don't need to bother with the `super.` calls. – Timmmm Oct 18 '12 at 12:26
  • Actually... yes you do. Although the default implementation doesn't save any state, it does note whether you've called it and complains if you don't (presumably a debugging aid). You can just add `super.onSaveInstanceState()` and `super.onRestoreInstanceState(null)` to satisfy it. – Timmmm Oct 18 '12 at 14:39
  • So overall, it's fine like this? :-) – Kobor42 Oct 25 '12 at 09:01
  • 5
    Thanks this way is much better and avoids BadParcelableException when using the SavedState framework for custom views since the saved state seems to be unable to set the class loader correctly for your custom SavedState! – Ian Warwick Jun 19 '13 at 07:48
  • 3
    I have several instances of the same view in an activity. They all have unique id's in the xml. But still all of them gets the settings of the last view. Any ideas? – Christoffer Jun 21 '14 at 16:56
  • 16
    This solution might be ok, but it is definitely not safe. By implementing this you're assuming that base `View` state is not a `Bundle`. Of course, that is true at the moment, but you are relying on this current implementation fact that is not guaranteed to be true. – Dmitry Zaytsev Aug 08 '14 at 14:50
  • 1
    Would there be a reason why `state instanceof Bundle` wouldn't be true in `onRestoreInstanceState` ? – Snaker Jul 15 '15 at 20:51
  • 3
    @Christoffer Helped me: [Saving Android View state correctly](http://trickyandroid.com/saving-android-view-state-correctly/); at the end he uses [dispatchSaveInstanceState](http://developer.android.com/intl/es/reference/android/view/View.html#dispatchSaveInstanceState(android.util.SparseArray)) and [dispatchRestoreInstanceState](http://developer.android.com/intl/es/reference/android/view/View.html#dispatchRestoreInstanceState(android.util.SparseArray))! – EmmanuelMess Feb 29 '16 at 16:21
  • 1
    @DmitryZaitsev Why would you have to assume that? Doesn't Bundle implement Parcelable and thus putting a bundle into a bundle is fine? – A. Steenbergen Jun 02 '17 at 11:40
  • 2
    @A.Steenbergen `Bundle` is always a `Parcelable`, you are right on this one. But `Parcelable` is not always a `Bundle`. – Dmitry Zaytsev Jun 02 '17 at 13:18
  • But we are returning a Bundle from onSaveInstanceState, which we are getting back in onRestoreInstanceState so we can be sure to receive a bundle – A. Steenbergen Jun 02 '17 at 13:20
  • @A.Steenbergen Why typecheck? What if someone overrides your control, and does something with your "bundle"? Many things could go wrong, and it's a very hard to handle subclassing. Nice way is that they put your bundle into their own parcelable as a "superState", but you can't force this. This seemed a good idea. – Kobor42 Jul 10 '17 at 15:40
  • 1
    @Snaker It can be false if state is null. If not null, then typecheck should theoretically always be true. – Kobor42 Jul 10 '17 at 15:44
  • Be careful. In my case it causes memory leak! – Evgenii Aug 26 '22 at 19:34
424

You do this by implementing View#onSaveInstanceState and View#onRestoreInstanceState and extending the View.BaseSavedState class.

public class CustomView extends View {

  private int stateToSave;

  ...

  @Override
  public Parcelable onSaveInstanceState() {
    //begin boilerplate code that allows parent classes to save state
    Parcelable superState = super.onSaveInstanceState();

    SavedState ss = new SavedState(superState);
    //end

    ss.stateToSave = this.stateToSave;

    return ss;
  }

  @Override
  public void onRestoreInstanceState(Parcelable state) {
    //begin boilerplate code so parent classes can restore state
    if(!(state instanceof SavedState)) {
      super.onRestoreInstanceState(state);
      return;
    }

    SavedState ss = (SavedState)state;
    super.onRestoreInstanceState(ss.getSuperState());
    //end

    this.stateToSave = ss.stateToSave;
  }

  static class SavedState extends BaseSavedState {
    int stateToSave;

    SavedState(Parcelable superState) {
      super(superState);
    }

    private SavedState(Parcel in) {
      super(in);
      this.stateToSave = in.readInt();
    }

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

    //required field that makes Parcelables from a Parcel
    public static final Parcelable.Creator<SavedState> CREATOR =
        new Parcelable.Creator<SavedState>() {
          public SavedState createFromParcel(Parcel in) {
            return new SavedState(in);
          }
          public SavedState[] newArray(int size) {
            return new SavedState[size];
          }
    };
  }
}

The work is split between the View and the View's SavedState class. You should do all the work of reading and writing to and from the Parcel in the SavedState class. Then your View class can do the work of extracting the state members and doing the work necessary to get the class back to a valid state.

Notes: View#onSavedInstanceState and View#onRestoreInstanceState are called automatically for you if View#getId returns a value >= 0. This happens when you give it an id in xml or call setId manually. Otherwise you have to call View#onSaveInstanceState and write the Parcelable returned to the parcel you get in Activity#onSaveInstanceState to save the state and subsequently read it and pass it to View#onRestoreInstanceState from Activity#onRestoreInstanceState.

Another simple example of this is the CompoundButton

Daniel Lubarov
  • 7,796
  • 1
  • 37
  • 56
Rich Schuler
  • 41,814
  • 6
  • 72
  • 59
  • 15
    For those arriving here because this isn't working when using Fragments with the v4 support library, I note that the support library doesn't seem to call the View's onSaveInstanceState/onRestoreInstanceState for you; you have to explicitly call it yourself from a convenient place in the FragmentActivity or Fragment. – magneticMonster Sep 15 '11 at 15:12
  • 74
    Note that the CustomView you apply this to should have a unique id set, otherwise they will share state with each other. SavedState is stored against the CustomView's id, so if you have multiple CustomViews with the same id, or no id, then the parcel saved in the final CustomView.onSaveInstanceState() will be passed into all the calls to CustomView.onRestoreInstanceState() when the views are restored. – Nick Street Oct 03 '11 at 12:04
  • 5
    This method didn't work for me with two custom views (one extending the other). I kept getting a ClassNotFoundException when restoring my view. I had to use the Bundle approach in Kobor42's answer. – Chris Feist Sep 21 '12 at 21:00
  • 3
    `onSaveInstanceState()` and `onRestoreInstanceState()` should be `protected` (like their superclass), not `public`. No reason to expose them... – XåpplI'-I0llwlg'I - Jan 11 '13 at 10:27
  • 7
    This **doesn't work well** when saving a custom `BaseSaveState` for a class that extends RecyclerView, you get `Parcel﹕ Class not found when unmarshalling: android.support.v7.widget.RecyclerView$SavedState java.lang.ClassNotFoundException: android.support.v7.widget.RecyclerView$SavedState` so you need to do the bug fix that's written down here: https://github.com/ksoichiro/Android-ObservableScrollView/commit/6f915f7482635c1f4889281c7941a586f2cfb611 (using the ClassLoader of RecyclerView.class to load the super state) – EpicPandaForce Jul 18 '15 at 18:59
  • 1
    The recyclerview workaround is here by the author of ObservableScrollView library: https://github.com/ksoichiro/Android-ObservableScrollView/blob/6f915f7482635c1f4889281c7941a586f2cfb611/observablescrollview/src/main/java/com/github/ksoichiro/android/observablescrollview/ObservableRecyclerView.java#L252-L350 – EpicPandaForce Jul 18 '15 at 19:04
  • This seems to save the state, but when I run it writeToParcel is never called. The data is just saved in the fields of SavedState – dylan7 Jan 20 '16 at 23:47
  • @EpicPandaForce have you used this solution ? – Nininea Oct 20 '16 at 19:52
  • @Nininea yes, although lately I've been using the workaround-version I posted afterwards which contains an explicit "superState". – EpicPandaForce Oct 20 '16 at 21:33
  • 1
    I am getting ClassCastException in the OnRestoreInstanceState() method – KingKongCoder Sep 20 '17 at 06:08
  • Answer from @kobor42 works well with Bundle object, this example gives me ClassCastException i've checked all my custom view id's. – KingKongCoder Sep 20 '17 at 06:36
  • @rich-schuler May I ask for your help on why a [custom image view looses state on Marshmallow](https://stackoverflow.com/questions/55087954/custom-image-view-looses-state-on-marshmallow)? – JJD Mar 12 '19 at 09:53
  • This is causing crash with message : Unmarshalling unknown type code 2131165303 at offset 3748 – Napolean Aug 05 '19 at 13:15
  • This answer is quite old and I noticed there was a constructor missing that caused crashes on newer Android versions. Have a look at my answer when you are getting a ClassNotFoundException: https://stackoverflow.com/a/59299308/2212770 – Wirling Dec 12 '19 at 07:08
  • The `CREATOR` field must to be `public static`, otherwise the state can't restore in some case. – zeleven Feb 22 '21 at 14:43
25

Easy with kotlin

@Parcelize
class MyState(val superSavedState: Parcelable?, val loading: Boolean) : View.BaseSavedState(superSavedState), Parcelable


class MyView : View {

    var loading: Boolean = false

    override fun onSaveInstanceState(): Parcelable? {
        val superState = super.onSaveInstanceState()
        return MyState(superState, loading)
    }

    override fun onRestoreInstanceState(state: Parcelable?) {
        val myState = state as? MyState
        super.onRestoreInstanceState(myState?.superSaveState ?: state)

        loading = myState?.loading ?: false
        //redraw
    }
}
Zakhar Rodionov
  • 1,398
  • 16
  • 18
18

Here is another variant that uses a mix of the two above methods. Combining the speed and correctness of Parcelable with the simplicity of a Bundle:

@Override
public Parcelable onSaveInstanceState() {
    Bundle bundle = new Bundle();
    // The vars you want to save - in this instance a string and a boolean
    String someString = "something";
    boolean someBoolean = true;
    State state = new State(super.onSaveInstanceState(), someString, someBoolean);
    bundle.putParcelable(State.STATE, state);
    return bundle;
}

@Override
public void onRestoreInstanceState(Parcelable state) {
    if (state instanceof Bundle) {
        Bundle bundle = (Bundle) state;
        State customViewState = (State) bundle.getParcelable(State.STATE);
        // The vars you saved - do whatever you want with them
        String someString = customViewState.getText();
        boolean someBoolean = customViewState.isSomethingShowing());
        super.onRestoreInstanceState(customViewState.getSuperState());
        return;
    }
    // Stops a bug with the wrong state being passed to the super
    super.onRestoreInstanceState(BaseSavedState.EMPTY_STATE); 
}

protected static class State extends BaseSavedState {
    protected static final String STATE = "YourCustomView.STATE";

    private final String someText;
    private final boolean somethingShowing;

    public State(Parcelable superState, String someText, boolean somethingShowing) {
        super(superState);
        this.someText = someText;
        this.somethingShowing = somethingShowing;
    }

    public String getText(){
        return this.someText;
    }

    public boolean isSomethingShowing(){
        return this.somethingShowing;
    }
}
Blundell
  • 75,855
  • 30
  • 208
  • 233
  • 3
    This doesn't work. I get a ClassCastException... And that's because it needs a public static CREATOR so that it instantiates your `State` from the parcel. Please take a look at: http://charlesharley.com/2012/programming/views-saving-instance-state-in-android/ – mato Aug 06 '15 at 16:28
11

The answers here already are great, but don't necessarily work for custom ViewGroups. To get all custom Views to retain their state, you must override onSaveInstanceState() and onRestoreInstanceState(Parcelable state) in each class. You also need to ensure they all have unique ids, whether they're inflated from xml or added programmatically.

What I came up with was remarkably like Kobor42's answer, but the error remained because I was adding the Views to a custom ViewGroup programmatically and not assigning unique ids.

The link shared by mato will work, but it means none of the individual Views manage their own state - the entire state is saved in the ViewGroup methods.

The problem is that when multiple of these ViewGroups are added to a layout, the ids of their elements from the xml are no longer unique (if its defined in xml). At runtime, you can call the static method View.generateViewId() to get a unique id for a View. This is only available from API 17.

Here is my code from the ViewGroup (it is abstract, and mOriginalValue is a type variable):

public abstract class DetailRow<E> extends LinearLayout {

    private static final String SUPER_INSTANCE_STATE = "saved_instance_state_parcelable";
    private static final String STATE_VIEW_IDS = "state_view_ids";
    private static final String STATE_ORIGINAL_VALUE = "state_original_value";

    private E mOriginalValue;
    private int[] mViewIds;

// ...

    @Override
    protected Parcelable onSaveInstanceState() {

        // Create a bundle to put super parcelable in
        Bundle bundle = new Bundle();
        bundle.putParcelable(SUPER_INSTANCE_STATE, super.onSaveInstanceState());
        // Use abstract method to put mOriginalValue in the bundle;
        putValueInTheBundle(mOriginalValue, bundle, STATE_ORIGINAL_VALUE);
        // Store mViewIds in the bundle - initialize if necessary.
        if (mViewIds == null) {
            // We need as many ids as child views
            mViewIds = new int[getChildCount()];
            for (int i = 0; i < mViewIds.length; i++) {
                // generate a unique id for each view
                mViewIds[i] = View.generateViewId();
                // assign the id to the view at the same index
                getChildAt(i).setId(mViewIds[i]);
            }
        }
        bundle.putIntArray(STATE_VIEW_IDS, mViewIds);
        // return the bundle
        return bundle;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {

        // We know state is a Bundle:
        Bundle bundle = (Bundle) state;
        // Get mViewIds out of the bundle
        mViewIds = bundle.getIntArray(STATE_VIEW_IDS);
        // For each id, assign to the view of same index
        if (mViewIds != null) {
            for (int i = 0; i < mViewIds.length; i++) {
                getChildAt(i).setId(mViewIds[i]);
            }
        }
        // Get mOriginalValue out of the bundle
        mOriginalValue = getValueBackOutOfTheBundle(bundle, STATE_ORIGINAL_VALUE);
        // get super parcelable back out of the bundle and pass it to
        // super.onRestoreInstanceState(Parcelable)
        state = bundle.getParcelable(SUPER_INSTANCE_STATE);
        super.onRestoreInstanceState(state);
    } 
}
mmBs
  • 8,421
  • 6
  • 38
  • 46
Fletcher Johns
  • 1,236
  • 15
  • 20
  • Custom id is really an issue, but I think it should be handled at the initialization of the view, and not at state save. – Kobor42 Jan 15 '16 at 18:12
  • Good point. Do you suggest setting mViewIds in the constructor then overwrite if state is restored? – Fletcher Johns Jan 18 '16 at 11:39
  • This is the most complete answer that is valid for inflatable custom layouts. Unfortunately the answer generates ids only for the direct children but usually that's not the case. E.g. the children could be TextInputEditText inside TextInputLayout. In this case the solution would be much more complex. – WindRider Jan 15 '21 at 19:30
4

I had the problem that onRestoreInstanceState restored all my custom views with the state of the last view. I solved it by adding these two methods to my custom view:

@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    dispatchFreezeSelfOnly(container);
}

@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
    dispatchThawSelfOnly(container);
}
chrigist
  • 41
  • 2
  • The dispatchFreezeSelfOnly and dispatchThawSelfOnly methods belong to ViewGroup, not View. So in case, your custom View is extended from a build-in View. Your solution is not applicable. – Harvey Sep 07 '19 at 09:32
2

To augment other answers - if you have multiple custom compound views with the same ID and they are all being restored with the state of the last view on a configuration change, all you need to do is tell the view to only dispatch save/restore events to itself by overriding a couple of methods.

class MyCompoundView : ViewGroup {

    ...

    override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
        dispatchFreezeSelfOnly(container)
    }

    override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
        dispatchThawSelfOnly(container)
    }
}

For an explanation of what is happening and why this works, see this blog post. Basically your compound view's children's view IDs are shared by each compound view and state restoration gets confused. By only dispatching state for the compound view itself, we prevent their children from getting mixed messages from other compound views.

Tom
  • 6,946
  • 2
  • 47
  • 63
2

I found that this answer was causing some crashes on Android versions 9 and 10. I think it's a good approach but when I was looking at some Android code I found out it was missing a constructor. The answer is quite old so at the time there probably was no need for it. When I added the missing constructor and called it from the creator the crash was fixed.

So here is the edited code:

public class CustomView extends LinearLayout {

    private int stateToSave;

    ...

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);

        // your custom state
        ss.stateToSave = this.stateToSave;

        return ss;
    }

    @Override
    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container)
    {
        dispatchFreezeSelfOnly(container);
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());

        // your custom state
        this.stateToSave = ss.stateToSave;
    }

    @Override
    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container)
    {
        dispatchThawSelfOnly(container);
    }

    static class SavedState extends BaseSavedState {
        int stateToSave;

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            this.stateToSave = in.readInt();
        }

        // This was the missing constructor
        @RequiresApi(Build.VERSION_CODES.N)
        SavedState(Parcel in, ClassLoader loader)
        {
            super(in, loader);
            this.stateToSave = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(this.stateToSave);
        }    
        
        public static final Creator<SavedState> CREATOR =
            new ClassLoaderCreator<SavedState>() {
          
            // This was also missing
            @Override
            public SavedState createFromParcel(Parcel in, ClassLoader loader)
            {
                return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ? new SavedState(in, loader) : new SavedState(in);
            }

            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in, null);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
}
Wirling
  • 4,810
  • 3
  • 48
  • 78
  • Did you try the code yourself? The method `dispatchFreezeSelfOnly` and `dispatchFreezeSelfOnly` are belong to `ViewGroup`, not `View`. – zeleven Feb 19 '21 at 08:52
  • @zeleven You're right, I used code from another answer and didn't edit it correctly. You should just extend `LinearLayout` or `ViewGroup` instead of `View` and it will be fine. I do use a slightly altered version of this solution in my app and it works fine. I Will try to edit my answer. – Wirling Feb 19 '21 at 11:14
1

Instead of using onSaveInstanceState and onRestoreInstanceState, you can also use a ViewModel. Make your data model extend ViewModel, and then you can use ViewModelProviders to get the same instance of your model every time the Activity is recreated:

class MyData extends ViewModel {
    // have all your properties with getters and setters here
}

public class MyActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // the first time, ViewModelProvider will create a new MyData
        // object. When the Activity is recreated (e.g. because the screen
        // is rotated), ViewModelProvider will give you the initial MyData
        // object back, without creating a new one, so all your property
        // values are retained from the previous view.
        myData = ViewModelProviders.of(this).get(MyData.class);

        ...
    }
}

To use ViewModelProviders, add the following to dependencies in app/build.gradle:

implementation "android.arch.lifecycle:extensions:1.1.1"
implementation "android.arch.lifecycle:viewmodel:1.1.1"

Note that your MyActivity extends FragmentActivity instead of just extending Activity.

You can read more about ViewModels here:

Benedikt Köppel
  • 4,853
  • 4
  • 32
  • 42
  • 1
    Please have a look at [Android ViewModel Architecture Component considered harmful](https://www.techyourchance.com/android-viewmodel-architecture-component-harmful) – JJD Mar 10 '19 at 10:57
  • 2
    @JJD I agree with the article you posted, one still has to handle save and restore properly. `ViewModel` is especially handy if you have large data sets to retain during a state change, such as a screen rotation. I prefer using the `ViewModel` instead of writing it into `Application` because it's clearly scoped, and I can have multiple Activities of the same Application behaving correctly. – Benedikt Köppel Mar 10 '19 at 21:19
1

Based on @Fletcher Johns answer I came up with:

  • custom layout
  • can inflate from XML
  • is able to save/restore direct and indirect children. I improved @Fletcher Johns' answer to save the ids in String->Id map instead of IntArray.
  • the only small drawback is that you must declare your saveable child views beforehand.

open class AddressView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0,
        defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {

    protected lateinit var countryInputLayout: TextInputLayout
    protected lateinit var countryAutoCompleteTextView: CountryAutoCompleteTextView
    protected lateinit var cityInputLayout: TextInputLayout
    protected lateinit var cityEditText: CityEditText
    protected lateinit var postCodeInputLayout: TextInputLayout
    protected lateinit var postCodeEditText: PostCodeEditText
    protected lateinit var streetInputLayout: TextInputLayout
    protected lateinit var streetEditText: StreetEditText
    
    init {
        initView()
    }

    private fun initView() {
        val view = inflate(context, R.layout.view_address, this)

        orientation = VERTICAL

        countryInputLayout = view.findViewById(R.id.countryInputLayout)
        countryAutoCompleteTextView = view.findViewById(R.id.countryAutoCompleteTextView)

        streetInputLayout = view.findViewById(R.id.streetInputLayout)
        streetEditText = view.findViewById(R.id.streetEditText)

        cityInputLayout = view.findViewById(R.id.cityInputLayout)
        cityEditText = view.findViewById(R.id.cityEditText)

        postCodeInputLayout = view.findViewById(R.id.postCodeInputLayout)
        postCodeEditText = view.findViewById(R.id.postCodeEditText)
    }

    // Declare your direct and indirect child views that need to be saved
    private val childrenToSave get() = mapOf<String, View>(
            "coutryIL" to countryInputLayout,
            "countryACTV" to countryAutoCompleteTextView,
            "streetIL" to streetInputLayout,
            "streetET" to streetEditText,
            "cityIL" to cityInputLayout,
            "cityET" to cityEditText,
            "postCodeIL" to postCodeInputLayout,
            "postCodeET" to postCodeEditText,
    )
    private var viewIds: HashMap<String, Int>? = null

    override fun onSaveInstanceState(): Parcelable? {
        // Create a bundle to put super parcelable in
        val bundle = Bundle()
        bundle.putParcelable(SUPER_INSTANCE_STATE, super.onSaveInstanceState())
        // Store viewIds in the bundle - initialize if necessary.
        if (viewIds == null) {
            childrenToSave.values.forEach { view -> view.id = generateViewId() }
            viewIds = HashMap<String, Int>(childrenToSave.mapValues { (key, view) -> view.id })
        }

        bundle.putSerializable(STATE_VIEW_IDS, viewIds)

        return bundle
    }

    override fun onRestoreInstanceState(state: Parcelable?) {
        // We know state is a Bundle:
        val bundle = state as Bundle
        // Get mViewIds out of the bundle
        viewIds = bundle.getSerializable(STATE_VIEW_IDS) as HashMap<String, Int>
        // For each id, assign to the view of same index
        if (viewIds != null) {
            viewIds!!.forEach { (key, id) -> childrenToSave[key]!!.id = id }
        }
        super.onRestoreInstanceState(bundle.getParcelable(SUPER_INSTANCE_STATE))
    }

    companion object {
        private const val SUPER_INSTANCE_STATE = "saved_instance_state_parcelable"
        private const val STATE_VIEW_IDS = "state_view_ids"
    }
}
WindRider
  • 11,958
  • 6
  • 50
  • 57