Question

I have a layout in which I have dynamically added custom views at a push of a button. These layouts extend LinearLayout and each carry their own unique Action objects.

The views will disappear, however, if onCreate is called again, when the user navigates away or rotates the screen. I want to keep these custom ActionHolder views there. To add to the problem, the ActionHolder objects contain sensitive information. The Action objects themselves store a live timer(that is supposed to keep on ticking even if the app is off), as well as other information.

According to an answer below, I have done the following, but to no avail. Here is what I have so far:

public class ActionHolder extends LinearLayout implements Serializable {

   /**
    * 
    */
   private static final long serialVersionUID = 2271402255369440088L;
   private Action action;
   private String timer;
   public static final int ACTION_TITLE = 0, ACTION_TIMER = 1,
         PAUSEANDPLAY_BTN = 2, FINISH_BTN = 3;



   public ActionHolder(Context context) {
      super(context);
   }



   public ActionHolder(Context context, AttributeSet attr) {
      super(context, attr);
   }



   public ActionHolder(Context context, AttributeSet attr, int defStyle) {
      super(context, attr, defStyle);
   }



   public void initiate(Action input) {
      // int hashedID = input.getActionName().hashCode();
      // if (hashedID < 0)
      // hashedID *= -1;
      // this.setId(hashedID);
      this.setOrientation(LinearLayout.VERTICAL);
      this.setLayoutParams(new LinearLayout.LayoutParams(
            LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
      action = input;
      LayoutInflater inflater = LayoutInflater.from(getContext());
      View view = inflater.inflate(R.layout.action_holder_layout, this, true);

      TextView actionTitle = (TextView) view
            .findViewById(com.tonimiko.mochi_bean.R.id.action_holder_title);
      actionTitle.setText(action.getActionName());
      actionTitle.setId(ActionHolder.ACTION_TITLE);

      TextView actionTimer = (TextView) view
            .findViewById(R.id.action_holder_timer);
      actionTimer.setId(ActionHolder.ACTION_TIMER);

      Button pauseBtn = (Button) view
            .findViewById(com.tonimiko.mochi_bean.R.id.pause_and_play_timer_btn);
      pauseBtn.setId(ActionHolder.PAUSEANDPLAY_BTN);

      Button finishBtn = (Button) view
            .findViewById(com.tonimiko.mochi_bean.R.id.finish_activity_button);
      finishBtn.setId(ActionHolder.FINISH_BTN);

      action.setActivityStartTime();
   }



   public Action finishAction() {
      action.setActivityStopTime();
      return action;
   }



   @Override
   protected void onLayout(boolean changed, int l, int t, int r, int b) {
      super.onLayout(changed, l, t, r, b);
   }



   public String toString() {
      return "Action stored: " + action.getActionName();
   }



   @Override
   public boolean equals(Object other) {
      ActionHolder otherObj = (ActionHolder) other;
      if (this.action.getActionName().toUpperCase()
            .equals(otherObj.action.getActionName().toUpperCase()))
         return true;
      return false;
   }



   @Override
   public int hashCode() {
      return action.getActionName().hashCode();
   }



   @Override
   protected Parcelable onSaveInstanceState() {
      Parcelable superState = super.onSaveInstanceState();
      Bundle data = new Bundle();
      data.putString("Timer", timer);
      data.putSerializable("Action", action);
      Log.e("debug", "View onSaveInstanceState called!"); // TODO
      Parcelable test = new ActionHolderSavedState(superState, data);
      if(test==null)
         Log.e("debug", "NULL PARCELABLE"); // TODO
      return new ActionHolderSavedState(superState, data);
   }



   @Override
   protected void onRestoreInstanceState(Parcelable state) {
      Log.e("debug", "View onRestore called!");
      if (state instanceof ActionHolderSavedState) {
         final ActionHolderSavedState savedState = (ActionHolderSavedState) state;
         this.action = savedState.getAction();
         this.timer = savedState.getTimer();
         // this.initiate(action);
         super.onRestoreInstanceState(savedState.getSuperState());
         Log.e("debug", "View onRestoreInstanceState finished"); // TODO
      }
   }

   static class ActionHolderSavedState extends BaseSavedState {

      private Action storedAction;
      private String storedTimer;



      public ActionHolderSavedState(Parcelable superState, Bundle data) {
         super(superState);
         storedTimer = data.getString("Timer");
         storedAction = (Action) data.getSerializable("Action");
      }



      private ActionHolderSavedState(Parcel in) {
         super(in);
         storedTimer = in.readString();
         storedAction = in.readParcelable(ActionHolder.class.getClassLoader());
      }



      public Action getAction() {
         return storedAction;
      }



      public String getTimer() {
         return storedTimer;
      }



      @Override
      public void writeToParcel(final Parcel out, final int flags) {
         super.writeToParcel(out, flags);
         out.writeString(storedTimer);
         out.writeSerializable(storedAction);
      }

      // required field that makes Parcelables from a Parcel
      public static final Parcelable.Creator<ActionHolderSavedState> CREATOR = new Parcelable.Creator<ActionHolderSavedState>() {

         public ActionHolderSavedState createFromParcel(final Parcel in) {
            return new ActionHolderSavedState(in);
         }



         public ActionHolderSavedState[] newArray(int size) {
            return new ActionHolderSavedState[size];
         }
      };
   }
}

Is there SOMETHING I am doing wrong? I've spend almost 4 days already on this.

Was it helpful?

Solution

I have a situation very similar to yours, with custom views being added dynamically to the screen and that need to save state when the activity is killed by the OS and recreated later, for example.

I'm overriding onSaveInstanceState on the custom view. It needs to return a Parcelable object. The key is to create a custom class that extends BaseSavedState and stores your data into that Parcelable. It would look somewhat like this:

@Override
protected Parcelable onSaveInstanceState() {
    final Parcelable state = super.onSaveInstanceState();
    return new ContainerLayoutSavedState(state, data);
}

@Override
protected void onRestoreInstanceState(final Parcelable state) {
    if (state instanceof ContainerLayoutSavedState) {
        final ContainerLayoutSavedState savedState = (ContainerLayoutSavedState)state;
        this.data = savedState.getData();
        super.onRestoreInstanceState(savedState.getSuperState());
    }
}

public static class ContainerLayoutSavedState extends BaseSavedState {
    private String data;

    ContainerLayoutSavedState(final Parcelable superState, final String data) {
        super(superState);
        // Here in this constructor you inject whatever you want to get saved into the Parcelable object. In this contrived example, we're just saving a string called data.
        this.data = data;
    }


    private ContainerLayoutSavedState(final Parcel in) {
        super(in);

        data = in.readString();
    }

    public String getData()
        return data;
    }

    @Override
    public void writeToParcel(final Parcel out, final int flags) {
        super.writeToParcel(out, flags);

        out.writeString(data);
    }

    // required field that makes Parcelables from a Parcel
    public static final Parcelable.Creator<ContainerLayoutSavedState> CREATOR = new Parcelable.Creator<ContainerLayoutSavedState>() {
        @Override
        public ContainerLayoutSavedState createFromParcel(final Parcel in) {
            return new ContainerLayoutSavedState(in);
        }

        @Override
        public ContainerLayoutSavedState[] newArray(final int size) {
            return new ContainerLayoutSavedState[size];
        }
    };
} }

Also, don't forget to set IDs to your dynamically added views, so they get re-added to the View tree when you come back.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top