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

Frustration-free orientation changes

19f33af017093834caecee97856322e6?s=47 cketti
April 24, 2018

Frustration-free orientation changes

Many apps do unexpected things when a user rotates the device. A message that has been painfully composed using the on-screen keyboard is just gone. Dialogs are disappearing. Data that took a while to load and display is gone and now the app is making the user wait again.

All of this is very frustrating to users. But if you know that an Activity is destroyed and then recreated on orientation change, this is not too surprising to you. Still, many beginners don’t know what is going on behind the scenes and consequently come up with flawed or incomplete ways to deal with the lifetime of an Activity.

If you have ever “fixed” a bug by changing your app’s manifest to manually handle orientation changes or by locking the device orientation, this talk is for you. It aims to give attendees a better understanding of the Activity lifetime/lifecycle. And we will look at the fundamental ways to deal with this situation. So you can write better apps.

19f33af017093834caecee97856322e6?s=128

cketti

April 24, 2018
Tweet

Transcript

  1. Frustration-free orientation changes cketti Twitter: @cketti

  2. Frustration?

  3. Frustration?

  4. Frustration!

  5. Frustration!

  6. Frustration!

  7. Frustration-free

  8. What happens during an orientation change? destroyed recreated User-perceived Activity

    lifetime Activity instance lifetime
  9. Dealing with Activity restarts

  10. Handle the orientation change yourself AndroidManifest.xml: <activity android:name=".SearchActivity" android:configChanges="orientation|screenSize" …

    />
  11. Handle the orientation change yourself SearchActivity.kt: override fun onConfigurationChanged(newConfig: Configuration)

    { super.onConfigurationChanged(newConfig) if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { portraitOnlyView.setVisibility(View.GONE) } else { portraitOnlyView.setVisibility(View.VISIBLE) } }
  12. Types of configuration changes • locale • density • fontScale

    • keyboard • keyboardHidden • layoutDirection • mcc • mnc • navigation • screenLayout • smallestScreenSize • touchscreen • uiMode • orientation • screenSize
  13. Retain an object during a configuration change • Activity.onRetainNonConfigurationInstance() +

    getLastNonConfigurationInstance() • FragmentActivity.onRetainCustomNonConfigurationInstance() + getLastCustomNonConfigurationInstance() • Loaders • Fragment.setRetainInstance(true) • Android Architecture Components: ViewModel
  14. Are we done?

  15. No.

  16. Android Low Memory Killer Image source: https://www.vecteezy.com/vector-art/66465-grim-reaper-tattoo

  17. Saving state

  18. onSaveInstanceState() override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putBoolean("optionalViewVisible", optionalView.isVisible) }

  19. Limitations “A Bundle object isn't appropriate for preserving more than

    a trivial amount of data because it consumes system-process memory” – The Activity Lifecycle | Android Developers
  20. onRestoreInstanceState() override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) optionalView.isVisible = savedInstanceState.getBoolean("optionalViewVisible")

    }
  21. Restoring state in onCreate() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)

    … if (savedInstanceState != null) { … } }
  22. What state is saved for you? • Activity stack •

    Fragment stack • Platform UI widgets save user-modifiable UI state ◦ Views need to have an ID set
  23. What state should you save? • As little as possible,

    as much as necessary • User input • Reference to original data source ◦ Database ID ◦ URL ◦ File name
  24. Consequences

  25. Timing java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState at

    android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2053) at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2079) at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:678) at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:632) …
  26. Timing state saved recreated Activity instance 1 Activity instance 2

    Background operation UI update
  27. Object references Don’t hold on to any object that references

    your Activity instance • Views • Inner classes in Activity
  28. Being restored while waiting for user input EditUserActivity ImagePickerActivity Load

    User object startActivityForResult() onActivityResult() Update User object
  29. Being restored while waiting for user input EditUserActivity ImagePickerActivity startActivityForResult()

    onActivityResult() Load User object Update User object Load User object
  30. Testing

  31. Manual testing • Rotate the device • Change language

  32. Manual testing • Rotate the device • Change language •

    Change font size
  33. Manual testing • Rotate the device • Change language •

    Change font size • Change display size
  34. Manual testing • Rotate the device • Change language •

    Change font size • Change display size • Developer options ◦ Don’t keep activities
  35. Manual testing • Rotate the device • Change language •

    Change font size • Change display size • Developer options ◦ Don’t keep activities ◦ Background process limit
  36. Summary • Always save state • Retain an object as

    optimization • Don’t update the UI when you’re not supposed to • Check out ViewModel + LiveData
  37. Thank you! cketti Twitter: @cketti Email: hello@cketti.de