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

Where Did My State Go? - WWC Mobile

Where Did My State Go? - WWC Mobile

Subhrajyoti Sen

December 22, 2020
Tweet

More Decks by Subhrajyoti Sen

Other Decks in Programming

Transcript

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

    View Slide

  2. This is a state management talk

    View Slide

  3. This is a Lifecycle talk

    View Slide

  4. The First thing

    View Slide

  5. The First thing
    We have all done this

    View Slide

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

    View Slide

  7. Is this Enough?

    View Slide

  8. NO

    View Slide

  9. NO

    View Slide

  10. But WHY?

    View Slide

  11. Reasons

    View Slide

  12. Reasons
    1. Dark Theme

    View Slide

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

    View Slide

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

    View Slide

  15. Reasons
    1. Dark Theme
    2. Multi-Window
    3. Process Death
    4. Changing Language/ Font Scale

    View Slide

  16. Reasons
    1. Dark Theme
    2. Multi-Window
    3. Process Death
    4. Changing Language/ Font Scale
    5. Permission Revocation

    View Slide

  17. Dark Theme

    View Slide

  18. Dark Theme
    Introduced in Android 10

    View Slide

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

    View Slide

  20. Dark Theme

    View Slide

  21. Dark Theme
    Triggers a uiMode configuration change

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  25. Multi-Window

    View Slide

  26. Multi-Window

    View Slide

  27. Multi-Window

    View Slide

  28. Multi-Window
    Introduced in Android N

    View Slide

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

    View Slide

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

    View Slide

  31. 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

  32. Multi-Window
    Portrait orientation != Vertical device

    View Slide

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

    View Slide

  34. Process Death

    View Slide

  35. Process Death

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

    View Slide

  36. 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

  37. 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

  38. 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

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

    View Slide

  40. Process Death - Testing

    View Slide

  41. ViewModel

    View Slide

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

    View Slide

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

    View Slide

  44. ViewModel - SavedStateHandle

    View Slide

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

    View Slide

  46. 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

  47. onSaveInstanceState is still valid
    class ProfileBottomSheet() : BottomSheetDialogFragment() {
    private lateinit var name: String
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    button.setOnClickListener { name = editText.text.toString()}
    }
    override fun onSaveInstanceState(outState: Bundle) {
    outState.putString("name", name)
    super.onSaveInstanceState(outState)
    }
    }

    View Slide

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

    View Slide

  49. 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

  50. 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

  51. 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

  52. 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

  53. Fragment
    class ProfileBottomSheet(val name: String) : BottomSheetDialogFragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    textview.setText(name)
    }
    }

    View Slide

  54. Fragment - Use Bundle
    val profileBottomSheet = ProfileBottomSheet()
    profileBottomSheet.arguments = Bundle().apply {
    putString("name", "subhrajyoti")
    }

    View Slide

  55. What about TESTING?

    View Slide

  56. Testing Activity/Fragment Recreation

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  60. Testing Fragment Recreation

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  65. Testing Fragment Lifecycle

    View Slide

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

    View Slide

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

    View Slide

  68. Resources
    https://andrewbailey.dev/falsehoods/lifecycles
    https://www.techyourchance.com/testing-android-applications-killing/

    View Slide

  69. Thank You
    @iamsubhrajyoti

    View Slide