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

Where Did My State Go? - WWC Mobile

Where Did My State Go? - WWC Mobile

53b72671be580e70c9795c7eaf35ac12?s=128

Subhrajyoti Sen

December 22, 2020
Tweet

Transcript

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

  2. This is a state management talk

  3. This is a Lifecycle talk

  4. The First thing

  5. The First thing We have all done this

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

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

  8. NO

  9. NO

  10. But WHY?

  11. Reasons

  12. Reasons 1. Dark Theme

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

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

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

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

    Changing Language/ Font Scale 5. Permission Revocation
  17. Dark Theme

  18. Dark Theme Introduced in Android 10

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

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

  21. Dark Theme Triggers a uiMode configuration change

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

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

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

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

  26. Multi-Window

  27. Multi-Window

  28. Multi-Window Introduced in Android N

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

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

    You can’t completely depend on onPause() and onResume()
  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
  32. Multi-Window Portrait orientation != Vertical device

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

    of Multi-Window
  34. Process Death

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

    and the OS kills your app to free up memory.
  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.
  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.
  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
  39. Process Death - Testing 1. Put the app in background

    2. adb shell am kill packagename
  40. Process Death - Testing

  41. ViewModel

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

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

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

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

  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")?: "" } }
  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) } }
  48. Custom View Views can save their own state. Views need

    to have an ID to be able to save state.
  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) } }
  50. 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) } } }
  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 } }
  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 } }
  53. Fragment class ProfileBottomSheet(val name: String) : BottomSheetDialogFragment() { override fun

    onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) textview.setText(name) } }
  54. Fragment - Use Bundle val profileBottomSheet = ProfileBottomSheet() profileBottomSheet.arguments =

    Bundle().apply { putString("name", "subhrajyoti") }
  55. What about TESTING?

  56. Testing Activity/Fragment Recreation

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

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

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

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

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

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

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

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

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

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

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

    { val scenario = launchFragmentInContainer<MyFragment>() scenario.moveToState(State.CREATED) } }
  68. Resources https://andrewbailey.dev/falsehoods/lifecycles https://www.techyourchance.com/testing-android-applications-killing/

  69. Thank You @iamsubhrajyoti