8

I have a custom view, let's say this is its code:

public class CustomView extends View {

    boolean visible;
    boolean enabled;

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView, 0, 0);
        try {
            visible = a.getBoolean(R.styleable.CustomView_visible, true);
            enabled = a.getBoolean(R.styleable.CustomView_enabled, true);
        } finally {
            a.recycle();
        }

        // Apply XML attributes here
    }

    @Override
    public Parcelable onSaveInstanceState() {
        // Save instance state
        Bundle bundle = new Bundle();
        bundle.putParcelable("superState", super.onSaveInstanceState());
        bundle.putBoolean("visible", visible);
        bundle.putBoolean("enabled", enabled);

        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        // Restore instance state
        // This is called after constructor
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            visible = bundle.getBoolean("visible");
            enabled = bundle.getBoolean("enabled");

            state = bundle.getParcelable("superState");
        }
        super.onRestoreInstanceState(state);
    }
}

Pretty straightforward. My custom view reads attributes from XML and applies them. These attributes are saved and restored on configuration changes.

But if I have two different layout, for example for two different orientations:

[layout-port/view.xml]
<CustomView
    custom:visible="true"
    custom:enabled="true"

[layout-land/view.xml]
<CustomView
    custom:visible="false"
    custom:enabled="false"

My problem is that when changing the device orientation, view state is saved as visible and enabled, but now XML layout states the view shouldn't have either. Constructor gets called before onRestoreInstanceState and the XML attributes are getting overwritten by the saved state. I don't want that, XML has priority over saved state.

I am doing something wrong? What would be the best way to solve this ?

Nicolas
  • 6,611
  • 3
  • 29
  • 73
  • store the xml values in other variables and reapply them after restoration. You can also don't apply restoration, so the values will always be those defined in xml – nandsito Apr 22 '17 at 15:55
  • @nandsito This is probably what I end up doing. I just thought maybe there was a more direct way to do that, a way to restore state after parsing XML. What I thought I could do is to save the AttributeSet to a variable then parse XML at the end of onRestoreInstanteState. But is onRestoreInstanteState is not called when view is first created. – Nicolas Apr 23 '17 at 00:00
  • android parses the xml and applies its attributes in the view constructor, so the xml is always processed before the restore state. If you want to change this order, you'll have to manually set the variables values – nandsito Apr 23 '17 at 00:07
  • Don't save & restore particularly these two attributes. They reflect state of some content or piece of data or model in general. So the state of view should be set at runtime. – Eugen Pechanec Apr 26 '17 at 17:02
  • @EugenPechanec That's what I ended up doing. – Nicolas Apr 26 '17 at 22:04

2 Answers2

0

In your case you have to store current orientation in Parcelable along with other attributes, and apply those restored attributes only in the case if restored orientation is equal to the current orientation (i.e. activity was destroyed and restored by OS). In your case I would use android:tag for defining current orientation like this:

[layout-port/view.xml]
<CustomView
    android:tag="port"
    custom:visible="true"
    custom:enabled="true"

[layout-land/view.xml]
<CustomView
    android:tag="land"
    custom:visible="false"
    custom:enabled="false"

And then your custom view class would be like:

public class ScheduleView extends View {

    String orientation;
    boolean visible;
    boolean enabled;

    public ScheduleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView, 0, 0);
        try {
            visible = a.getBoolean(R.styleable.CustomView_visible, true);
            enabled = a.getBoolean(R.styleable.CustomView_enabled, true);
        } finally {
            a.recycle();
        }

        orientation = (String) getTag();
    }

    @Override
    public Parcelable onSaveInstanceState() {
        // Save instance state
        Bundle bundle = new Bundle();
        bundle.putParcelable("superState", super.onSaveInstanceState());
        bundle.putBoolean("visible", visible);
        bundle.putBoolean("enabled", enabled);
        bundle.putString("orientation", orientation);

        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        // Restore instance state
        // This is called after constructor
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;

            String restoredOrientation = bundle.getString("orientation");
            if (restoredOrientation.equals(orientation)) {
                visible = bundle.getBoolean("visible");
                enabled = bundle.getBoolean("enabled");
            }

            state = bundle.getParcelable("superState");
        }
        super.onRestoreInstanceState(state);
    }
}

Haven't tested it properly, but it should work. Hope it will be helpful.

romtsn
  • 11,704
  • 2
  • 31
  • 49
  • I want to apply some saved attributes, those that are not in XML. Same thing applies to azizbekian's answer. – Nicolas Apr 22 '17 at 23:41
  • Sorry, but I don't get you problem. You actually could apply those out-of-xml attributes outside `if` block in `onRestoreInstanceState` method. – romtsn Apr 23 '17 at 05:31
  • `onRestoreInstanceState` is not always called this is the problem. – Nicolas Apr 23 '17 at 12:05
  • Well, then I guess you need to explain your problem better. You said ***saved*** attributes - by ***saved*** I understand that you want to apply those attributes after `onRestoreInstanceState` called. – romtsn Apr 23 '17 at 12:15
0

Basically, you need to separate states to portrait and landscape, meaning you also have to save particular configuration state also.

final boolean isPortrait = 
            getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
bundle.putBoolean("isPortrait", isPortrait);

Then when restoring the state:

final boolean savedOrientation = bundle.getBoolean("isPortrait");
final boolean currentOrientation = 
            getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;

if (savedOrientation == currentOrientation) {
    // now retrieve saved values
} else {
    // do nothing, values are initialized in constructor
}
azizbekian
  • 60,783
  • 13
  • 169
  • 249