Save 37% off PRO during our Black Friday Sale! »

Where Did My State Go?

Where Did My State Go?

53b72671be580e70c9795c7eaf35ac12?s=128

Subhrajyoti Sen

July 24, 2020
Tweet

Transcript

  1. Where Did My State Go? Subhrajyoti Sen @iamsubhrajyoti

  2. This is a Lifecycle talk

  3. The First thing

  4. The First thing We have all done this

  5. The First thing We have all done this <activity android:name=".main.MainActivity"

    android:screenOrientation="portrait">
  6. Is this Enough?

  7. NO

  8. NO

  9. But WHY?

  10. Reasons

  11. Reasons 1. Dark Theme

  12. Reasons 1. Dark Theme 2. Multi-Window

  13. Reasons 1. Dark Theme 2. Multi-Window 3. Process Death

  14. Dark Theme

  15. Dark Theme Introduced in Android 10

  16. Dark Theme Introduced in Android 10 Can be changed by

    1. System Toggle 2. AppCompat in-app
  17. Dark Theme

  18. Dark Theme Triggers a uiMode configuration change

  19. Dark Theme Triggers a uiMode configuration change All started Activities

    are automatically recreated.
  20. Dark Theme <activity android:name=".MyActivity" android:configChanges="uiMode" />

  21. Dark Theme <activity android:name=".MyActivity" android:configChanges="uiMode" /> This will invoke onConfigurationChanged()

    and not recreate all started Activities.
  22. Multi-Window

  23. Multi-Window

  24. Multi-Window

  25. Multi-Window Introduced in Android N

  26. Multi-Window Introduced in Android N A slight change in Lifecycle.

  27. Multi-Window Introduced in Android N A slight change in Lifecycle.

    You can’t completely depend on onPause() and onResume()
  28. Multi-Window Introduced in Android N A slight change in Lifecycle.

    You can’t completely depend on onPause() and onResume() screenOrientation will be ignored
  29. Multi-Window Portrait orientation != Vertical device

  30. Multi-Window Portrait orientation != Vertical device android:resizeableActivity="false" to opt out

    of Multi-Window
  31. Process Death

  32. Process Death • The device is running low on memory

    and the OS kills your app to free up memory.
  33. Process Death • The device is running low on memory

    and the OS kills your app to free up memory. • It happens if your app is in the Paused/Stopped state.
  34. Process Death • The device is running low on memory

    and the OS kills your app to free up memory. • It happens if your app is in the Paused/Stopped state. • Cannot work around it.
  35. Process Death - Testing 1. Developer Options -> Apps ->

    Background process limit 2. Choose ‘No background processes’ 3. Open your app and put in the background 4. Open another app and switch to your app
  36. Process Death - Testing 1. Put the app in background

    2. adb shell am kill packagename
  37. ViewModel

  38. ViewModel ViewModels persist state across Activity recreation. ViewModels do not

    persist state on process death.
  39. ViewModel ViewModels persist state across Activity recreation. ViewModels do not

    persist state on process death. Use SavedStateHandle to save state.
  40. ViewModel - SavedStateHandle

  41. ViewModel - SavedStateHandle val viewModel = ViewModelProvider(this, SavedStateVMFactory(this)) .get(MyViewModel::class.java)

  42. ViewModel - SavedStateHandle val viewModel = ViewModelProvider(this, SavedStateVMFactory(this)) .get(MyViewModel::class.java) class

    MyViewModel(val state : SavedStateHandle) : ViewModel() { fun saveProductId(productId: String) { state.set("productId", productId) } fun getProductId(): String { return state.get("productId")?: "" } }
  43. Custom View Views can save their own state. Views need

    to have an ID to be able to save state.
  44. Custom View private class SavedState : BaseSavedState { var value

    = 1 constructor(superState: Parcelable) : super(superState) private constructor(source: Parcel) : super(source) { value = source.readInt() } override fun writeToParcel(out: Parcel, flags: Int) { super.writeToParcel(out, flags) out.writeInt(value) } }
  45. Custom View private class SavedState : BaseSavedState { //.. companion

    object { val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> { override fun createFromParcel(source: Parcel) = SavedState(source) override fun newArray(size: Int) = arrayOfNulls<SavedState?>(size) } } }
  46. Custom View class CustomView(context: Context) : View(context) { var value

    = 1 override fun onSaveInstanceState(): Parcelable { val superState = super.onSaveInstanceState() val state = SavedState(superState) state.value = this.value return state } }
  47. Custom View class CustomView(context: Context) : View(context) { override fun

    onRestoreInstanceState(state: Parcelable) { val savedState = state as SavedState super.onRestoreInstanceState(savedState.superState) this.value = savedState.value } }
  48. What about TESTING?

  49. Testing Activity/Fragment Recreation

  50. Testing Activity/Fragment Recreation Can be done with Espresso

  51. Testing Activity/Fragment Recreation Can be done with Espresso Easier with

    AndroidX Test
  52. Testing Activity/Fragment Recreation Can be done with Espresso Easier with

    AndroidX Test debugImplementation 'androidx.fragment:fragment-testing:$fragment_version'
  53. Testing Fragment Recreation

  54. Testing Fragment Recreation @RunWith(AndroidJUnit4::class) class MyTestSuite { @Test fun testRecreation()

    { val scenario = launchFragmentInContainer<MyFragment>() } }
  55. Testing Fragment Recreation @RunWith(AndroidJUnit4::class) class MyTestSuite { @Test fun testRecreation()

    { val scenario = launchFragmentInContainer<MyFragment>() // perform actions } }
  56. Testing Fragment Recreation @RunWith(AndroidJUnit4::class) class MyTestSuite { @Test fun testRecreation()

    { val scenario = launchFragmentInContainer<MyFragment>() // perform actions scenario.recreate() } }
  57. Testing Fragment Recreation @RunWith(AndroidJUnit4::class) class MyTestSuite { @Test fun testRecreation()

    { val scenario = launchFragmentInContainer<MyFragment>() // perform actions scenario.recreate() // test events } }
  58. Testing Fragment Lifecycle

  59. Testing Fragment Lifecycle @RunWith(AndroidJUnit4::class) class MyTestSuite { @Test fun testLifecycle()

    { val scenario = launchFragmentInContainer<MyFragment>() } }
  60. Testing Fragment Lifecycle @RunWith(AndroidJUnit4::class) class MyTestSuite { @Test fun testLifecycle()

    { val scenario = launchFragmentInContainer<MyFragment>() scenario.moveToState(State.CREATED) } }
  61. Thank You @iamsubhrajyoti