Share
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
Transcript
78 slides
-
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 . -
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 -
BE A GOOD CITIZEN 6 Disclaimer • Not a formal
study • Extracted from personal observations -
BE A GOOD CITIZEN 7 Pre-requisites • Understanding of Activities
• Understanding of resource qualifiers -
BE A GOOD CITIZEN 8 So, what are we talking
about? • Activity/fragment lifecycle • Window size (responsiveness) -
BE A GOOD CITIZEN 9 There is nothing new here!
• You’re right! • These concerns exist since the stone age (~2009) -
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 -
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 -
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 -
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 -
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 -
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) -
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 -
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. -
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 -
BE A GOOD CITIZEN 2 4 Activity Lifecycle Problematic How
to keep data between recreation so that its transparent to the user? -
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 -
26 BE A GOOD CITIZEN Good Citizen Rule # 1
Never assume that something that you previously kept in memory is still available. -
BE A GOOD CITIZEN 2 8 Activity Lifecycle Save the
data Where to save data? • saveInstanceState bundle (for small things) • On disk -
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 -
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 -
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) -
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? -
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? -
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 -
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 -
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 -
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 -
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. -
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 -
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 -
BE A GOOD CITIZEN 4 3 What about the Activity
inputs / outputs? -
44 BE A GOOD CITIZEN Good Citizen Rule # 3
Activity input (parameters) must be lightweight and minimal -
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 -
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? -
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 -
BE A GOOD CITIZEN 4 9 Activity Lifecycle Input example
Product List Activity Product Details Activity Serializable / Parcelable Product Model The wrong way -
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 -
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 -
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 -
53 BE A GOOD CITIZEN Good Citizen Rule # 4
Respect the Activity “pattern” -
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 -
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 -
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 -
58 BE A GOOD CITIZEN Good Citizen Rule # 5
Always base your layout decisions on width and height -
BE A GOOD CITIZEN What width and height? • Folders
with -wXXXdp and -hXXXdp qualifiers layout-w600dp values-h400dp 5 9 Responsiveness Width and height -
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 -
BE A GOOD CITIZEN List VS list/details 6 1 Responsiveness
Width and height layout/list.xml layout-w800dp/list.xml -
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 -
BE A GOOD CITIZEN Vertical constraint example 6 3 Responsiveness
Width and height Margin between texts has a different value at h600dp -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
BE A GOOD CITIZEN Another reason why Android apps on
ChromeOS will get more important... 7 4 Why? ChromeOS -
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. -
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 -
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