Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Where Did My State Go?

Where Did My State Go?

Subhrajyoti Sen

July 24, 2020
Tweet

More Decks by Subhrajyoti Sen

Other Decks in Technology

Transcript

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

    View Slide

  2. This is a Lifecycle talk

    View Slide

  3. The First thing

    View Slide

  4. The First thing
    We have all done this

    View Slide

  5. The First thing
    We have all done this
    android:name=".main.MainActivity"
    android:screenOrientation="portrait">

    View Slide

  6. Is this Enough?

    View Slide

  7. NO

    View Slide

  8. NO

    View Slide

  9. But WHY?

    View Slide

  10. Reasons

    View Slide

  11. Reasons
    1. Dark Theme

    View Slide

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

    View Slide

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

    View Slide

  14. Dark Theme

    View Slide

  15. Dark Theme
    Introduced in Android 10

    View Slide

  16. Dark Theme
    Introduced in Android 10
    Can be changed by
    1. System Toggle
    2. AppCompat in-app

    View Slide

  17. Dark Theme

    View Slide

  18. Dark Theme
    Triggers a uiMode configuration change

    View Slide

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

    View Slide

  20. Dark Theme
    android:name=".MyActivity"
    android:configChanges="uiMode" />

    View Slide

  21. Dark Theme
    android:name=".MyActivity"
    android:configChanges="uiMode" />
    This will invoke onConfigurationChanged() and not recreate all started Activities.

    View Slide

  22. Multi-Window

    View Slide

  23. Multi-Window

    View Slide

  24. Multi-Window

    View Slide

  25. Multi-Window
    Introduced in Android N

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  29. Multi-Window
    Portrait orientation != Vertical device

    View Slide

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

    View Slide

  31. Process Death

    View Slide

  32. Process Death

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

    View Slide

  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.

    View Slide

  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.

    View Slide

  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

    View Slide

  36. Process Death - Testing
    1. Put the app in background
    2. adb shell am kill packagename

    View Slide

  37. ViewModel

    View Slide

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

    View Slide

  39. ViewModel
    ViewModels persist state across Activity recreation.
    ViewModels do not persist state on process death.
    Use SavedStateHandle to save state.

    View Slide

  40. ViewModel - SavedStateHandle

    View Slide

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

    View Slide

  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")?: ""
    }
    }

    View Slide

  43. Custom View
    Views can save their own state.
    Views need to have an ID to be able to save state.

    View Slide

  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)
    }
    }

    View Slide

  45. Custom View
    private class SavedState : BaseSavedState {
    //..
    companion object {
    val CREATOR: Parcelable.Creator =
    object : Parcelable.Creator {
    override fun createFromParcel(source: Parcel) = SavedState(source)
    override fun newArray(size: Int) = arrayOfNulls(size)
    }
    }
    }

    View Slide

  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
    }
    }

    View Slide

  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
    }
    }

    View Slide

  48. What about TESTING?

    View Slide

  49. Testing Activity/Fragment Recreation

    View Slide

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

    View Slide

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

    View Slide

  52. Testing Activity/Fragment Recreation
    Can be done with Espresso
    Easier with AndroidX Test
    debugImplementation 'androidx.fragment:fragment-testing:$fragment_version'

    View Slide

  53. Testing Fragment Recreation

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  58. Testing Fragment Lifecycle

    View Slide

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

    View Slide

  60. Testing Fragment Lifecycle
    @RunWith(AndroidJUnit4::class)
    class MyTestSuite {
    @Test fun testLifecycle() {
    val scenario = launchFragmentInContainer()
    scenario.moveToState(State.CREATED)
    }
    }

    View Slide

  61. Thank You
    @iamsubhrajyoti

    View Slide