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

Be a good citizen – Care about Android lifecycl...

Be a good citizen – Care about Android lifecycle and window size by Guillaume Bourassa

Properly handling app lifecycle and window size is a challenge as old as Android itself. However, even today, many developers still don’t care enough about it, either because they don’t really understand the issue or because they think that they don’t really need to.
With Google pushing ChromeOS more and more, Android apps that work properly in a multi-window environment is more important than ever. And this requires proper lifecycle and window size understanding.

This talk will explain the different challenges of lifecycle handling, how to properly craft layouts that fits in any window size and, above all, why Android developers should care about all that. And why “my app is phone-only and locked in portrait” is not a valid reason to ignore these aspects.

https://android-montreal.com/2019/04/09/april-meetup-2019/

GDG Montreal

April 24, 2019
Tweet

More Decks by GDG Montreal

Other Decks in Programming

Transcript

  1. BE A GOOD CITIZEN 2 1. About me 2. About

    Mirego 3. What are we talking about? 4. Android (activity) lifecycle 5. Properly supporting multiple window size 6. Why should we care about all that? Agenda .
  2. BE A GOOD CITIZEN 3 About me . Guillaume Bourassa,

    Android Developer since 2012. Cogeco Radio Bell Fibe TV Varage Sale V Mobile Metric Pagan Portal Sky Motion Groupe Capitale Médias Air Transat La Presse+ La Capitale IGA SAQ
  3. BE A GOOD CITIZEN 6 Disclaimer • Not a formal

    study • Extracted from personal observations
  4. BE A GOOD CITIZEN 7 Pre-requisites • Understanding of Activities

    • Understanding of resource qualifiers
  5. BE A GOOD CITIZEN 8 So, what are we talking

    about? • Activity/fragment lifecycle • Window size (responsiveness)
  6. BE A GOOD CITIZEN 9 There is nothing new here!

    • You’re right! • These concerns exist since the stone age (~2009)
  7. BE A GOOD CITIZEN 1 0 Ok, but why? •

    Because (some) people don’t care (enough) • Apps seem to work without these considerations • Lifecycle and responsiveness are the cornerstone of a rock steady, future-proof app • More on the philosophical why at the end
  8. BE A GOOD CITIZEN 1 4 Activity Lifecycle Types of

    recreation • Activity recreation ◦ Most frequent case ◦ Can cause UX frustrations ◦ Most important to handle • Process recreation ◦ Less frequent ◦ Causes more crashes ◦ Almost never happens during development
  9. BE A GOOD CITIZEN 1 5 Activity Lifecycle Activity recreation

    When? • On configuration change ◦ Device orientation change ◦ Multi-window activation / update ◦ Window resize on ChromeOS
  10. BE A GOOD CITIZEN 1 6 Activity Lifecycle Activity recreation

    onCreate Instance A onStart onResume onStop onSaveInstance State onPause onDestroy Instance B onCreate onStart onResume Bundle
  11. BE A GOOD CITIZEN 1 7 Activity Lifecycle Activity recreation

    What happens to other Activities • The whole stack gets re-created • Only the top first, then the others upon unstacking What happens to everything else • Application doesn’t change • Any other object in memory remain • Java process is alive and in good health
  12. BE A GOOD CITIZEN 1 8 Activity Lifecycle Activity recreation

    Can configuration changes be handled manually? • Yes, but don’t do it • Activity is not recreated when window size change, onConfigurationChanged() is called • Views are re-layouted in new Window • Window-size specific resources won’t be used with the new updated window dimensions (more on that later)
  13. BE A GOOD CITIZEN 1 9 Activity Lifecycle Activity recreation

    Enter transitions • Activity are recreated without running their enter transition • If you rely on transition end to do something, it won’t happen • if (savedInstanceState == null) is the only way to check in what case you are
  14. BE A GOOD CITIZEN 2 1 Activity Lifecycle Process recreation

    When? • When Android decides to kill your app while in background (to free memory or because its angry at your app) • When the user tries to return to the app, Android restores the Activity stack.
  15. BE A GOOD CITIZEN 2 2 Activity Lifecycle Process recreation

    onCreate Activity A onStart onResume onStop onPause Activity B onCreate onStart onResume Activity B Application Activity A onCreate
  16. BE A GOOD CITIZEN 2 4 Activity Lifecycle Problematic How

    to keep data between recreation so that its transparent to the user?
  17. BE A GOOD CITIZEN 2 5 Activity Lifecycle Save the

    data What is saved/restored • All standard Views inner state • All Fragments present in the FragmentManager (more on that later) Intent extras • Intent (and its “extras” Bundle) is still around What is not saved • Custom views state • Anything kept as a class member • For process recreation: literally everything else
  18. 26 BE A GOOD CITIZEN Good Citizen Rule # 1

    Never assume that something that you previously kept in memory is still available.
  19. BE A GOOD CITIZEN 2 8 Activity Lifecycle Save the

    data Where to save data? • saveInstanceState bundle (for small things) • On disk
  20. BE A GOOD CITIZEN 2 9 Activity Lifecycle Save the

    data onSaveInstanceState • Data must be Serializable / Parcelable • Use with cautious • Don’t use it to save data, otherwise... java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 76000872 bytes
  21. BE A GOOD CITIZEN 3 0 Activity Lifecycle Save the

    data The disk • Keep every backend response in a persisted cache (don’t rely on HTTP cache) • Gives a better user experience • Can be hard/costly to implement
  22. BE A GOOD CITIZEN 3 1 Activity Lifecycle Save the

    data ViewModels to the rescue • MVVM is a great pattern • Works well with data binding • ViewModels survive Activity recreation (not process recreation)
  23. BE A GOOD CITIZEN 3 2 Activity Lifecycle Save the

    data override fun onCreate() { super.onCreate() val viewModel = ViewModelProviders.of(this).get(AwesomeViewModel::class.java) } How do ViewModel survive?
  24. BE A GOOD CITIZEN 3 3 Activity Lifecycle Save the

    data public ViewModelStore getViewModelStore() { if (mViewModelStore == null) { NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null) { // Restore the ViewModelStore from NonConfigurationInstances mViewModelStore = nc.viewModelStore; } if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); } } return mViewModelStore; } How do ViewModel survive?
  25. 35 BE A GOOD CITIZEN Good Citizen Rule # 2

    Opinion warning It’s okay if activity and process recreation are not handled with the same level of transparency for the user
  26. BE A GOOD CITIZEN 3 6 Activity Lifecycle Level of

    recreation Activity recreation • Happens when app is active (user is actively using it) • Orientation change often happens without intention • User expects things to remain the same ◦ Loading screen is annoying ◦ Losing state is frustrating • Must be transparent
  27. BE A GOOD CITIZEN Process recreation • Happens when app

    is in background • Been there for a while • User comes back much later • Probable doesn’t even expect to get back to where he left ◦ Loading screen is acceptable ◦ Losing UI state is acceptable ◦ Crashing is not an option 3 7 Activity Lifecycle Level of recreation
  28. BE A GOOD CITIZEN Fragment recreation • Fragments get automatically

    recreated upon Activity recreation • Apply the same rules • Fragments decide if their instance is kept upon recreation (retainInstance = true) • 3 fragment rules to keep in mind 3 9 Activity Lifecycle Fragments
  29. BE A GOOD CITIZEN Fragments gets recreated, don’t add them

    blindly during onCreate() 4 0 Activity Lifecycle Fragment rule #1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate() setContentView(R.layout.activity_other) supportFragmentManager.beginTransaction() .add(FragmentOne(), "one") .add(FragmentTwo(), "two") .commit() } Two new fragments will be added to the fragment manager each time Activity is recreated.
  30. BE A GOOD CITIZEN • The Fragment instance you have

    might not be the right one when you need it • Always get Fragment instances from the SupportFragmentManager (or child) when you need it 4 1 Activity Lifecycle Fragment rule #2 Never keep Fragments as Activity class member
  31. BE A GOOD CITIZEN • Callback can end up in

    the wrong Activity • Callback can be null • Rule of thumb: Fragments talk to their Activity, not the other way around ◦ getActivity() ◦ Shared ViewModel (Google suggestion) 4 2 Activity Lifecycle Fragment rule #3 Never set a listener on a Fragment
  32. 44 BE A GOOD CITIZEN Good Citizen Rule # 3

    Activity input (parameters) must be lightweight and minimal
  33. BE A GOOD CITIZEN • An Activity must have clear

    inputs, when needed • Inputs must be passed to an Activity exclusively through its Intent extra Bundle • If possible use a library that help enforce them • Inputs must be as lightweight as possible 4 5 Activity Lifecycle Inputs
  34. BE A GOOD CITIZEN 4 7 Activity Lifecycle Input example

    Product List Activity Product Details Activity Product Model How to give a complex model object to another Activity?
  35. BE A GOOD CITIZEN 4 8 Activity Lifecycle Input example

    Product List Activity Product Details Activity Product Model The bad way Application / some singleton / static field ID
  36. BE A GOOD CITIZEN 4 9 Activity Lifecycle Input example

    Product List Activity Product Details Activity Serializable / Parcelable Product Model The wrong way
  37. BE A GOOD CITIZEN Why using Parcelable complex objects as

    Activity inputs is a bad idea 5 0 Activity Lifecycle Inputs as Parcelable Product List Activity Product Details Activity Serializable / Parcelable Product Model • Size limitations ◦ Size allowed gets smaller over ◦ Your object will get larger over time • Activity gets more coupled with the context from where its started ◦ Caller must have the same complex object ◦ Reduce re-use
  38. BE A GOOD CITIZEN 5 1 Activity Lifecycle Inputs The

    “good” way Product List Activity Product Details Activity Component that will get the data from where it exists ID Database File-based cache Rest API
  39. BE A GOOD CITIZEN • Activity input must be the

    minimal amount of information needed to perform its task • Information is then used to get the actual data 5 2 Activity Lifecycle Minimal inputs Product List Activity Product Details Activity ID
  40. 53 BE A GOOD CITIZEN Good Citizen Rule # 4

    Respect the Activity “pattern”
  41. BE A GOOD CITIZEN What is an Activity? • A

    standalone part of an app that must be able to live on its own • Must not rely on anything else in memory to do its job (if it needs something, it must get it from a reliable source) • Must be re-usable in different contexts • Its inputs must be clear, minimal, and lightweight 5 4 Activity Lifecycle Pattern
  42. BE A GOOD CITIZEN How to layout stuff? • Apps

    draw stuff in a window with fixed dimensions (width / height) • Fixed, but variable ◦ From device to device ◦ Over time on the same device (config change) 5 6 Responsiveness Philosophy
  43. BE A GOOD CITIZEN • Is the Activity running in

    landscape or portrait ◦ Portrait: layout-sw600dp/main.xml ◦ Landscape: layout-sw600dp-land/main.xml 5 7 Responsiveness Philosophy Bad way of deciding how to layout • Is the Activity running on a tablet or a phone? ◦ Phone: layout/main.xml ◦ Tablet: layout-sw600dp/main.xml
  44. 58 BE A GOOD CITIZEN Good Citizen Rule # 5

    Always base your layout decisions on width and height
  45. BE A GOOD CITIZEN What width and height? • Folders

    with -wXXXdp and -hXXXdp qualifiers layout-w600dp values-h400dp 5 9 Responsiveness Width and height
  46. BE A GOOD CITIZEN Why width and height? • It’s

    the only way to make sure your layout will work in every contexts (orientation, multi-window, floating window) • Nothing else matters • Almost every space constraint is either vertical or horizontal 6 0 Responsiveness Width and height
  47. BE A GOOD CITIZEN List VS list/details 6 1 Responsiveness

    Width and height layout/list.xml layout-w800dp/list.xml
  48. BE A GOOD CITIZEN 6 2 Responsiveness Width and height

    List VS grid • Same activity layout for all 4 cases • Header part has different layout at w800dp • List item has a different layout at w600dp • <integer> value for column count
  49. BE A GOOD CITIZEN Vertical constraint example 6 3 Responsiveness

    Width and height Margin between texts has a different value at h600dp
  50. BE A GOOD CITIZEN • layout-sw600dp/main.xml • Used to be

    the way to know if we were on a tablet • Represent the smallest between the width of the window in portrait and the one in landscape • Gives you a general idea of the size of the device, but not that much 6 4 Responsiveness Smallest Width
  51. BE A GOOD CITIZEN Don’t use Smallest Width qualifier (-swXXXdp)

    for layouting decisions 6 5 Responsiveness Width and height • Both these windows have a smallestWidth of 432dp • Their dimensions are totally opposite • There is nothing you can decide based on smallestWidth
  52. BE A GOOD CITIZEN Multi-window related notes • Never use

    different Activity classes for different window size (spoiler: it will crash) • Lifecycle is funky ◦ Vary with Android versions ◦ When activating, current Activity is re-created but not resumed ◦ Important to properly restore state to avoid unloaded screen or infinite loading 6 6 Responsiveness Multi-window
  53. BE A GOOD CITIZEN Easy to ignore all of this

    My app is phone-only, locked in portrait, and I deactivated multi-window. My activities are never recreated. 6 8 Why? No easy answer
  54. BE A GOOD CITIZEN • Who are we to decide

    how users will use their app? • Locking in portrait is a user decision, not a developer decision • Most of Google apps do not lock orientation 6 9 Why? Supporting multiple orientations Using an app in portrait is a personal decision
  55. BE A GOOD CITIZEN • Even if your app is

    “phone only”, users can and will run them on their tablets. • Without minimal care, your layouts will look terrible • Portrait-locked apps on tablets is frustrating 7 0 Why? Supporting “tablets” Your app will run on tablets
  56. BE A GOOD CITIZEN Tablet-optimized version of an app •

    Support from day 1 is almost free • Migrating a “phone-only, locked in portrait” app to a tablet-friendly is expensive 7 1 Why? No easy answer
  57. BE A GOOD CITIZEN Multi-window does matter • Seems useless

    at first (especially on phone) • Google works hard to promote ChromeOS ◦ Running Android apps on desktop is an awesome use case ◦ Running fullscreen Android apps on desktop is a terrible experience ◦ Pushes multi-window to the next level 7 2 Why? No easy answer
  58. BE A GOOD CITIZEN Another reason why Android apps on

    ChromeOS will get more important... 7 4 Why? ChromeOS
  59. 75 BE A GOOD CITIZEN Good Citizen Rule # 6

    Always support multi-window and orientation change. This will result in a better user experience, and will help you detect broken rules.
  60. BE A GOOD CITIZEN Because we are professionals developers •

    Do the right thing from day one • Don’t take shortcut • Don’t give the burden of fixing your current bad decisions to future you • Don’t give the burden of fixing your current bad decisions to future others 7 6 Why? The final reason
  61. BE A GOOD CITIZEN In software development, drifting from the

    right way of doing things, using shortcuts and “we’ll fix that later”, will leave you after some time with a messy, un-maintainable and horrific codebase. - Me 7 7 Why? Citation