Slide 1

Slide 1 text

Deep Dive Into Android State Restoration

Slide 2

Slide 2 text

TWITTER @cyrilmottier ! WEBSITE cyrilmottier.com

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

The story of a newbie Android developer

Slide 8

Slide 8 text

Kevin has just developed his first Android app

Slide 9

Slide 9 text

He discovers an annoying bug: Fields are cleared on rotate

Slide 10

Slide 10 text

3 options

Slide 11

Slide 11 text

3 options Don’t care

Slide 12

Slide 12 text

3 options Don’t care Block orientation

Slide 13

Slide 13 text

3 options Don’t care Block orientation Use configChanges Hint: all options are

Slide 14

Slide 14 text

1 ! 4 ! 5 ! 6 ! 7

Slide 15

Slide 15 text

Kevin’s satisfied

Slide 16

Slide 16 text

Still having issues on…

Slide 17

Slide 17 text

language changes Still having issues on…

Slide 18

Slide 18 text

1

Slide 19

Slide 19 text

ANGRY! Angry Kevin is

Slide 20

Slide 20 text

1 ! 4 ! 5 ! 6 ! 7

Slide 21

Slide 21 text

The nightmare continues… Still having issues when moving the app to the background

Slide 22

Slide 22 text

God save the STATE

Slide 23

Slide 23 text

State restoration key components

Slide 24

Slide 24 text

The container Parcel

Slide 25

Slide 25 text

Parcelable The container Parcel The content Primitives types Primitives arrays

Slide 26

Slide 26 text

The content Primitives types Primitives arrays 1 parcel.writeInt(1);! 2 parcel.writeLong(2L);! 3 parcel.writeFloat(3F);! 4 parcel.writeString("Hi!"); Parcelable

Slide 27

Slide 27 text

1 parcel.writeIntArray(new int[]{1, 2, 3});! 2 parcel.writeLongArray(new long[]{1L, 2L, 3L});! 3 parcel.writeDoubleArray(new double[]{1, 2, 3});! 4 parcel.writeStringArray(new String[]{! 5 "Hi", "Droidcon", "guys!"! 6 }); The content Primitives types Primitives arrays Parcelable

Slide 28

Slide 28 text

1 public final class Suggestion implements Parcelable {! 2 ! 3 public final String id;! 4 public final String name;! 5 public final int type;! 6 ! 7 public Suggestion(String id, String name, int type) {! 8 this.id = Objects.requireNonNull(id);! 9 this.name = Objects.requireNonNull(name);! 10 this.type = type;! 11 }! 12 ! 13 }

Slide 29

Slide 29 text

1 @Override! 2 public int describeContents() {! 3 return 0;! 4 }! 5 ! 6 @Override! 7 public void writeToParcel(Parcel dest, int flags) {! 8 dest.writeString(id);! 9 dest.writeString(name);! 10 dest.writeInt(type);! 11 }! 12 13 public static final Parcelable.Creator CREATOR = ! 14 new Parcelable.Creator() {! 15 public Suggestion createFromParcel(Parcel in) {! 16 return new Suggestion(in.readString(), // ! 17 in.readString(), //! 18 in.readInt());! 19 }! 20 ! 21 public Suggestion[] newArray(int size) {! 22 return new Suggestion[size];! 23 }! 24 };

Slide 30

Slide 30 text

Parcelable.Creator! The base creator interface ! Parcelable.ClassLoaderCreator! A creator with the ClassLoader passed on read. ! ParcelableCompat & ParcelableCompatCreatorCallbacks! Compatibility stuff

Slide 31

Slide 31 text

Bundle A key-value map & type-safe Parcelable

Slide 32

Slide 32 text

Parcel! internally uses reflection (required to get the CREATOR instance) …i.e. beware Proguard

Slide 33

Slide 33 text

Activity level state restoration

Slide 34

Slide 34 text

onCreate(null)

Slide 35

Slide 35 text

onCreate(null)

Slide 36

Slide 36 text

: non-null Bundle onSaveInstanceState( ) onCreate(null)

Slide 37

Slide 37 text

onSaveInstanceState( ) onCreate( ) onCreate(null) : non-null Bundle

Slide 38

Slide 38 text

onSaveInstanceState( ) onCreate( ) onRestoreInstanceState( ) onCreate(null) : non-null Bundle

Slide 39

Slide 39 text

onSaveInstanceState( ) onCreate( ) onRestoreInstanceState( ) onCreate(null) : non-null Bundle

Slide 40

Slide 40 text

onSaveInstanceState( ) onCreate( ) onRestoreInstanceState( ) onCreate(null) : non-null Bundle

Slide 41

Slide 41 text

What to save? Non persistent or non reconstructible info

Slide 42

Slide 42 text

1 public class SearchActivity extends Activity {! 2 ! 3 private static final String STATE_OUTWARD = "state:outward";! 4 ! 5 private DateComponents mOutward;! 6 ! 7 @Override! 8 protected void onCreate(Bundle inState) {! 9 super.onCreate(inState);! 10 ! 11 if (inState != null) {! 12 DateComponents components = inState.getParcelable(STATE_OUTWARD);! 13 if (components != null) {! 14 setOutward(components);! 15 }! 16 }! 17 }! 18 ! 19 @Override! 20 protected void onSaveInstanceState(Bundle outState) {! 21 super.onSaveInstanceState(outState);! 22 outState.putParcelable(STATE_OUTWARD, mOutward);! 23 }! 24 }

Slide 43

Slide 43 text

onSaveInstanceState saves Window

Slide 44

Slide 44 text

Window Fragments onSaveInstanceState saves

Slide 45

Slide 45 text

Window Fragments Dialogs onSaveInstanceState saves

Slide 46

Slide 46 text

Always call the SUPER METHODS Android has no guards on save-related methods

Slide 47

Slide 47 text

android:stateNotNeeded For restart-on-crash apps only (i.e. launcher app)

Slide 48

Slide 48 text

Developer options Don’t keep activities

Slide 49

Slide 49 text

Developer options Don’t keep activities

Slide 50

Slide 50 text

View level state restoration

Slide 51

Slide 51 text

Android saves UI state AUTOMAGICALLY

Slide 52

Slide 52 text

Android saves UI state AUTOMAGICALLY (aka “It just works!™”)

Slide 53

Slide 53 text

Android saves UI state AUTOMAGICALLY (aka “It just works!™”) …except in some cases

Slide 54

Slide 54 text

Works out-of-the-box if Views 1. Have an ID 2. Are “save” enabled 3. Come from the framework

Slide 55

Slide 55 text

saveHierarchyState() It always begins with a call to

Slide 56

Slide 56 text

EditText! @id/text RelativeLayout! @id/container CheckBox! @id/check_box TextView

Slide 57

Slide 57 text

EditText! @id/text RelativeLayout! @id/container CheckBox! @id/check_box TextView SparseArray

Slide 58

Slide 58 text

RelativeLayout! @id/container CheckBox! @id/check_box TextView SparseArray S1 EditText! @id/text

Slide 59

Slide 59 text

@id/container SparseArray RelativeLayout! @id/container CheckBox! @id/check_box TextView S1 EditText! @id/text

Slide 60

Slide 60 text

EditText! @id/text RelativeLayout! @id/container CheckBox! @id/check_box TextView @id/container S1 S2 SparseArray

Slide 61

Slide 61 text

@id/text @id/container S1 S2 SparseArray RelativeLayout! @id/container CheckBox! @id/check_box TextView EditText! @id/text

Slide 62

Slide 62 text

RelativeLayout! @id/container CheckBox! @id/check_box TextView @id/text @id/container S1 S2 SparseArray S3 EditText! @id/text

Slide 63

Slide 63 text

@id/text @id/container @id/check_box S1 S2 S3 SparseArray RelativeLayout! @id/container CheckBox! @id/check_box TextView EditText! @id/text

Slide 64

Slide 64 text

Controlling save setSaveEnabled(boolean) setSaveFromParentEnabled(boolean)

Slide 65

Slide 65 text

restoreHierarchyState() It always ends with a call to

Slide 66

Slide 66 text

@id/container S1 @id/text S2 @id/check_box S3 SparseArray RelativeLayout! @id/container CheckBox! @id/check_box TextView EditText! @id/text

Slide 67

Slide 67 text

@id/container S1 S2 S3 @id/text @id/check_box SparseArray RelativeLayout! @id/container CheckBox! @id/check_box TextView EditText! @id/text

Slide 68

Slide 68 text

RelativeLayout! @id/container CheckBox! @id/check_box TextView @id/container S1 S2 @id/check_box S3 S1 @id/text SparseArray EditText! @id/text

Slide 69

Slide 69 text

@id/container S1 @id/text S2 @id/check_box S3 SparseArray RelativeLayout! @id/container CheckBox! @id/check_box TextView EditText! @id/text

Slide 70

Slide 70 text

EditText! @id/text RelativeLayout! @id/container CheckBox! @id/check_box TextView S2 @id/container S1 @id/text @id/check_box S3 S2 SparseArray

Slide 71

Slide 71 text

@id/container S1 @id/text S2 @id/check_box S3 SparseArray RelativeLayout! @id/container CheckBox! @id/check_box TextView EditText! @id/text

Slide 72

Slide 72 text

RelativeLayout! @id/container CheckBox! @id/check_box TextView @id/container S1 @id/text S2 @id/check_box S3 S3 SparseArray EditText! @id/text

Slide 73

Slide 73 text

Ensure your Views’ IDs are unique & constant

Slide 74

Slide 74 text

1 static class SavedState extends BaseSavedState {! 2 int checked;! 3 ! 4 SavedState(Parcelable superState) { super(superState); }! 5 ! 6 private SavedState(Parcel in) {! 7 super(in);! 8 checked = in.readInt();! 9 }! 10 ! 11 @Override! 12 public void writeToParcel(Parcel out, int flags) {! 13 super.writeToParcel(out, flags);! 14 out.writeInt(checked);! 15 }! 16 ! 17 public static final Parcelable.Creator CREATOR = ! 18 new Parcelable.Creator() {! 19 public SavedState createFromParcel(Parcel in) {! 20 return new SavedState(in);! 21 }! 22 public SavedState[] newArray(int size) {! 23 return new SavedState[size];! 24 }! 25 };! 26 }

Slide 75

Slide 75 text

1 @Override! 2 public Parcelable onSaveInstanceState() {! 3 final Parcelable superState = super.onSaveInstanceState();! 4 SavedState ss = new SavedState(superState);! 5 ss.checked = isChecked() ? 1 : 0;! 6 return ss;! 7 }! 8 ! 9 @Override! 10 public void onRestoreInstanceState(Parcelable state) {! 11 SavedState ss = (SavedState) state;! 12 super.onRestoreInstanceState(ss.getSuperState());! 13 setChecked(ss.checked == 1);! 14 }

Slide 76

Slide 76 text

FrameLayout! @id/root ImageBox! @id/container1 CheckBox! @id/check_box ImageView! @id/image ImageBox! @id/container2 CheckBox! @id/check_box ImageView! @id/image

Slide 77

Slide 77 text

FrameLayout! @id/root ImageBox! @id/container1 ImageView! @id/image CheckBox! @id/check_box ImageBox! @id/container2 ImageView! @id/image CheckBox! @id/check_box Custom views with children with same IDs

Slide 78

Slide 78 text

1 static class SavedState extends BaseSavedState {! 2 ! 3 SparseArray childrenStates;! 4 ! 5 SavedState(Parcelable superState) { super(superState); }! 6 ! 7 private SavedState(Parcel in, ClassLoader loader) {! 8 super(in);! 9 childrenStates = in.readSparseArray(loader);! 10 }! 11 ! 12 @Override! 13 public void writeToParcel(Parcel out, int flags) {! 14 super.writeToParcel(out, flags);! 15 out.writeSparseArray(childrenStates);! 16 }! 17 ! 18 public static final Creator CREATOR = ParcelableCompat.! 19 newCreator(new ParcelableCompatCreatorCallbacks() {! 20 @Override! 21 public SavedState createFromParcel(Parcel source, ClassLoader loader) {! 22 return new SavedState(source, loader);! 23 }! 24 @Override! 25 public SavedState[] newArray(int size) {! 26 return new SavedState[size];! 27 }! 28 });! 29 }

Slide 79

Slide 79 text

1 @Override! 2 public Parcelable onSaveInstanceState() {! 3 final Parcelable superState = super.onSaveInstanceState();! 4 SavedState ss = new SavedState(superState);! 5 ss.childrenStates = new SparseArray();! 6 for (int i = 0; i < getChildCount(); i++) {! 7 getChildAt(i).saveHierarchyState(ss.childrenStates);! 8 }! 9 return ss;! 10 }! 11 ! 12 @Override! 13 public void onRestoreInstanceState(Parcelable state) {! 14 SavedState ss = (SavedState) state;! 15 super.onRestoreInstanceState(ss.getSuperState());! 16 for (int i = 0; i < getChildCount(); i++) {! 17 getChildAt(i).restoreHierarchyState(ss.childrenStates);! 18 }! 19 }

Slide 80

Slide 80 text

That has solved nothing! Still need to block save/restore dispatch

Slide 81

Slide 81 text

1 @Override! 2 protected void dispatchSaveInstanceState(SparseArray container) {! 3 dispatchFreezeSelfOnly(container);! 4 }! 5 ! 6 @Override! 7 protected void dispatchRestoreInstanceState(SparseArray container) {! 8 dispatchThawSelfOnly(container);! 9 }

Slide 82

Slide 82 text

Fragment level state restoration

Slide 83

Slide 83 text

Very similar to Activities state restoration lifecycle. (Fragments are tied to Activity after all)

Slide 84

Slide 84 text

Fragment blocks Activity save mechanism with framework setSaveFromParentEnabled(false) with support library NoSaveStateFrameLayout

Slide 85

Slide 85 text

2 distinct states Fragment + View common case View only detach, addToBackStack, etc.

Slide 86

Slide 86 text

Can to be used to create smooth transitions between your Activities: ! - Save the state SA of A - Start B with no animations passing SA - Apply SA to B - Transition between A and B was smooth Leveraging save/restore

Slide 87

Slide 87 text

Summarizing in three rules

Slide 88

Slide 88 text

Always save the state An Android app must survive configuration changes & low memory conditions.

Slide 89

Slide 89 text

Only save essential info Only save info that is non persistent or can not be reconstructed later.

Slide 90

Slide 90 text

Use correct levels Save instance states at the appropriate component level: Activity, Fragment or View.

Slide 91

Slide 91 text

Thank you! @cyrilmottier cyrilmottier.com

Slide 92

Slide 92 text

Resources Dressed for Iceland • Cécile Bernard Moelwynion, Eryri, Cymru • Marc Poppleton Happy, Confused, Wink, Sad, Angry • Megan Sheehan Floppy-Disk • Alex Auda Samora Fonts Source Sans Pro Courier