Tech Showcases,
Developer Resources &
Partners
droidcon News
Falsehoods Android Developers Believe about Lifecycles
Speaker: Andrew Bailey
Source: droidcon Online 2020
Hand-On & In-Depth Series Webinar (Part 3 of 4)
Transcript
en-us
00:00
so we can't talk about android without
00:02
talking about life cycles this
00:04
feels like a pretty obligatory thing in
00:06
our world
00:07
but i want to frame a little bit of a
00:09
different conversation about
00:11
life cycles because as software
00:13
developers it's sort of our job to make
00:15
assumptions
00:16
about our world we make assumptions
00:18
about what our
00:20
customers need and what our product
00:22
managers really want us to do
00:24
and we also make assumptions about how
00:25
we can model things so that they're easy
00:27
to digest
00:28
if we don't make these sort of
00:29
assumptions it's really impossible to
00:30
get any meaningful code written
00:32
whatsoever
00:34
the problem comes in where we make
00:36
assumptions about our frameworks and
00:37
about our platforms that don't hold up
00:40
because when we do things like this at
00:41
least to really obscure crashes and
00:43
subtle bugs that you may not even know
00:45
about
00:46
and i'm going to talk specifically about
00:49
life cycle related false assumptions
00:51
and depending on what sort of experience
00:53
you might have
00:54
you may be hearing me talk about these
00:56
and think that these are really
00:57
basic assumptions that are incorrect to
00:59
make i promise that more people are
01:01
making these
01:02
than you would think i work at big nerd
01:05
ranch and
01:06
you may think of us as more of a
01:07
training company but on a day-to-day
01:09
basis we do consulting
01:11
and we see a lot of projects that come
01:13
in and a lot of projects with a
01:14
surprising number of users are making
01:16
these sort of mistakes
01:18
so we're going to dig right into it and
01:19
we need to start at the very basics when
01:22
you worked on your first
01:23
android application it might have looked
01:26
something
01:26
like this so here's a really basic
01:28
counter application that i made
01:30
uh you've got an increment and a
01:32
decrement button and you can just count
01:34
some number of things that are happening
01:36
and this is all great but what you
01:37
probably noticed when you were working
01:39
on your first app
01:39
was when you rotate the phone this state
01:42
disappears
01:43
and if you're not expecting this this is
01:45
a really confusing experience to get
01:48
so the next steps are you probably did
01:50
some or all of these
01:52
but really the the uh the positive here
01:56
the the best case scenario is that you
01:57
end up going down the path of
01:59
implementing on save instance state
02:01
you go through all the song and dance of
02:03
writing that value
02:04
that counter uh incremented value into a
02:07
bundle
02:08
restore it out do all that song and
02:10
dance and you realize that these are a
02:12
lot of steps to go through
02:13
and you realize that this is a lot of
02:16
things to keep track of if you're
02:17
working on
02:18
some really big application and then
02:20
eventually you do start working on some
02:22
really big application
02:24
and one thing that you're very likely to
02:26
run into especially if you have a
02:27
designer is
02:28
they might ask you we don't want to
02:30
support landscape that's not really
02:32
something that we can design for
02:33
so can you lock the app to portrait and
02:36
when you hear this
02:38
you might have this reaction and
02:41
you may not notice that you've made an
02:43
incorrect assumption here
02:45
the assumption that you've made is that
02:46
the only reason that you'll lose state
02:48
in the only reason that your activity
02:49
restarts
02:50
is because of changing from portrait to
02:52
landscape
02:53
this is incorrect but we'll talk about
02:55
that more in just a second we haven't
02:57
gotten there yet
02:58
right now you're going into your android
03:00
manifest you're setting the screen
03:02
orientation to portrait you're adding a
03:04
lint suppression
03:05
um and then there's no orientation
03:08
changes anymore
03:09
so you don't have to worry about your
03:10
state being lost on a configuration
03:12
change
03:14
then somebody else on your team is going
03:16
to mention well
03:17
what about multi window are we doing
03:19
anything to support that
03:21
and you're not really sure how this
03:22
works so you go into your android
03:24
emulator and you enter multi window
03:27
and your state goes away so you do some
03:29
more research
03:30
and the really easy option is to turn
03:32
off multi-window support
03:34
and you can do that and it'll get you by
03:37
really quickly but you can sort of tell
03:38
that it's
03:39
not really the best goal and you've made
03:42
another assumption here
03:43
which is that there's a really small
03:45
number of reasons that your activity
03:47
that can be recreated and multi-window
03:50
is just
03:50
another one of them but you still might
03:53
not know this yet so going back into
03:54
your android manifest you
03:56
set resizable activity to false and now
03:58
you don't have to worry about
03:59
multi-window
04:00
anymore then somebody like your product
04:03
manager comes along and they say
04:05
well what about that samsung foldable
04:06
phone the really fancy and expensive one
04:09
are we doing anything to support that
04:11
and you might not know either so you
04:13
look up and you find that you can spin
04:15
up a foldable phone emulator you
04:17
run your app on the foldable phone and
04:19
everything looks great so far
04:21
but then you unfold the phone and
04:23
something really unexpected happens
04:26
because your app still has its state but
04:29
it's got all these black borders around
04:30
it it's kept the same
04:32
size that it had before this is a
04:34
compatibility mode that the system is
04:36
imposing on your app because you've set
04:38
resizable activity defaults
04:40
and you can see that there's a banner
04:41
that says you can make this app full
04:43
screen but you'll
04:44
have to restart it and if you click that
04:46
button then your state goes away
04:48
which is the exact same problem that
04:50
we've been seeing over and over again
04:52
so you do some more research and
04:54
depending on where you end up
04:55
you might see a stack overflow thread
04:57
that's guiding you to use the android
04:59
config changes property in your android
05:01
manifest
05:02
if you don't know really what's
05:04
happening here then you're actually
05:06
making two assumptions here
05:08
the first assumption is that config
05:10
changes is a really cheap way of
05:11
properly handling configuration changes
05:13
we'll get back to this in a second
05:16
the second assumption that you're making
05:17
here is that there is a way that you can
05:19
reliably prevent your activity from
05:21
restarting that's what we've been seeing
05:23
this whole time
05:24
both of these are incorrect but if you
05:26
didn't want to make this change in your
05:28
android manifest you can get rid of a
05:30
lot of the code from before you can add
05:32
this config changes slide here
05:34
even though you're specifying a lot of
05:35
things here you no longer have any limit
05:37
suppressions which it's
05:38
nice but this is overlooking a lot of
05:40
details and to dive more into this we
05:42
need to see all of the reasons that your
05:44
activity can restart
05:46
so far we've been talking about this
05:48
first group which falls into the bucket
05:50
of configuration changes
05:51
and more specifically we've been talking
05:53
about these two orientation and window
05:55
size which are
05:56
sort of the same thing if you think
05:58
about it from a really high level
05:60
but there's a lot of reasons that your
06:01
window can resize it's things like
06:03
multi-window it's foldable phones if
06:05
you're running on a chrome os device
06:06
then
06:07
you can run in a window and the user can
06:09
literally resize that window
06:11
but there's a lot of other types of
06:12
configuration changes that your activity
06:14
can restart from
06:15
a few other ones are language so if the
06:18
user changes their language then your
06:20
app
06:20
will restart or you'll get a
06:22
configuration change and the activity
06:23
will be recreated
06:25
you can also change the device and font
06:26
scaling factor to make things on screen
06:28
bigger or smaller
06:30
and the user can also change their sim
06:31
cards and depending on which version of
06:33
android you're running on that might
06:34
also
06:35
can trigger a configuration change and
06:36
there's more than these
06:38
so you can add these to that android
06:41
manifest config changes property
06:43
the downside here is that really the
06:45
goal of the config
06:46
changes attribute in xml is you're
06:48
telling the framework that you're going
06:50
to handle these types of configuration
06:52
changes
06:53
so if you didn't have alternative layout
06:55
resources for portrait or landscape or
06:57
different window sizes
06:58
you might not have noticed that
06:59
something unusual was happening but if
07:01
you have translations for your
07:02
application
07:03
and you put language inside of config
07:05
changes you'll notice that when the user
07:07
changes their language
07:08
all of the text inside of your
07:10
application will still be in the old
07:11
language and locale
07:13
so it won't be until you fetch resources
07:15
again that you start seeing
07:17
things in the updated language and
07:19
really you're supposed to go through and
07:21
manually set those strings yourself
07:23
which is
07:24
very complicated and that's why this
07:26
isn't recommended
07:27
and you're supposed to do that for the
07:28
other types of configuration changes as
07:30
well
07:32
there's also night mode which is a
07:34
little bit of an outlier here
07:36
night mode is special because it's
07:37
implemented a little bit by the
07:39
framework and a little bit by the
07:40
support library
07:41
and with nightmare in particular there
07:43
isn't a way to add it to the config
07:45
changes property it's not a supported
07:47
value
07:47
so if you do want to support night mode
07:49
and you do want to use the constructs
07:50
that the platform has provided to you
07:52
you don't really have a choice you have
07:54
to support configuration changes
07:57
and the really recommended way that you
07:59
do that is to use view model and at this
08:01
point
08:01
you're probably talking your platform
08:03
lead into doing this
08:05
and if you're using view model you
08:06
handle all of these situations in one
08:09
fell swoop
08:10
this is really recommended from an
08:11
architectural perspective it also
08:13
handles the case where you can
08:14
programmatically trigger a restart of
08:16
your activity manually
08:17
all of those will work but there's
08:20
another situation
08:22
if you just handle these you get most of
08:24
the activity recreations for you
08:26
those get handled but if you listen to
08:29
reports from your customers and
08:30
possibly from the google play store you
08:33
might hear reports of this
08:35
so you might hear users saying i was
08:37
doing something really important in the
08:39
application and then i went to look
08:40
something up
08:41
and by the time i came back all this
08:43
state was lost and i've been working on
08:44
that for hours so what
08:46
happened and that's because there's
08:48
another reason that the activity can be
08:50
recreated and there's a whole other
08:52
world to what's happening here
08:53
because there are scenarios where your
08:55
view models singletons and static
08:56
variables can all get wiped out and
08:58
re-initialized even if the user doesn't
08:60
leave your application
09:01
and that takes us into another category
09:03
of activity restarts but this which is
09:05
process eviction
09:06
and these behave a little bit
09:07
differently so to dive into this there's
09:10
a few reasons that android
09:12
will recreate your process uh it does
09:15
these the most well known one is low
09:16
memory
09:17
uh but the goal here is that android
09:19
should be able to kick your process from
09:21
memory
09:22
and it should be able to restore it so
09:24
that if the user does want to come back
09:25
to it they can do that hopefully without
09:27
noticing that anything has happened
09:30
there might be some more delay when
09:31
you're back when your application is
09:34
reloading and bringing everything into
09:35
memory but you should be able to return
09:38
to that
09:38
old state and the other thing about this
09:40
is when android kills your process it's
09:42
doing so very forcibly it's not going to
09:44
call on destroy for you it's just going
09:46
to do it
09:48
the primary way that we know that
09:50
android will do this
09:51
is with something called the viking
09:52
killer by a few people at google and
09:54
also by me
09:55
because that's a fantastic name more
09:57
formally it's known as
09:58
lmkd which is the low memory killer
10:00
demon the behavior varies device to
10:02
device but
10:03
basically the way that it works is if
10:05
there's significant memory pressure so
10:07
you're running like google chrome in the
10:08
video game or something
10:10
then eventually the system is going to
10:11
need to reclaim memory to keep things
10:14
working and what it'll do is it'll go
10:16
through applications that are in the
10:17
background it'll find ones that are
10:19
least recently used and it will evict
10:21
them out of memory and
10:23
keep their states so what that means is
10:25
if you
10:26
wrote to your onsave instance 8 bundle
10:28
that bundle will get persisted and
10:31
android will use that to recreate your
10:32
process later
10:34
and hopefully users don't know that this
10:36
is happening at all
10:38
the less common reason that your process
10:41
can restart actually relates to
10:42
permissions
10:43
because we have callbacks in place to
10:46
tell when our application is granted
10:47
permissions we
10:48
need to do those to show that dialogue
10:50
and respond to those results
10:52
but we don't have a callback for when a
10:53
user revokes permissions
10:55
the way that android actually implements
10:57
that is to kill the process and then
10:58
recreate it
10:59
because when we create it we have to go
11:01
and check all of those permissions again
11:04
um and that's the only really guaranteed
11:06
way that the framework has to make sure
11:07
that we're not doing anything
11:09
badly in practice this does behave the
11:11
exact same way as the viking killer
11:14
but it does also mean that you might be
11:16
making an incorrect assumption
11:17
that just because you checked for
11:19
permissions before launching an activity
11:21
or fragment
11:22
means that when the fragment or activity
11:24
is started or created that it has those
11:26
permissions
11:27
that might not be accurate if you've
11:29
revoked a permission
11:30
while you're on that screen
11:33
another thing that i do want to call out
11:35
is that there is a four stop button
11:37
on the app info page for every
11:39
application
11:40
and this has nothing to do with anything
11:42
that i've just talked about it's
11:43
completely different
11:45
the way that this button works is if the
11:46
application is running it will evict the
11:48
process immediately
11:50
but it also throws out all the savings
11:51
and state bundle so
11:53
you can't use this as a way to test how
11:55
your process
11:56
deaths are working so this raises the
11:59
obvious question
11:60
of if that button doesn't work then how
12:02
do i test this
12:04
and there's two really good ways of
12:05
testing this
12:07
one is you can do this with android
12:09
studio so if you run your app in android
12:11
studio
12:12
you can leave the application hit the
12:14
stop button and then reopen it on the
12:16
device and that triggers the same
12:17
scenarios
12:18
there is also a library called venom
12:20
which i did not make it will do the
12:22
exact same thing and it will give you a
12:24
notification
12:24
to do this if you're using
12:28
viewmodel the way that you can handle
12:29
this is to implement the save state
12:31
library
12:32
so at this point if this is an issue to
12:34
you you will probably want to start
12:36
talking your team
12:37
to using this and at this point your
12:39
android manifest
12:40
only has one really attribute for your
12:43
activity tag which is really clean
12:45
and the next uh the rest of your changes
12:47
are going to take place in your view
12:48
model
12:49
so if you have this counter view model
12:51
from before then
12:52
you'll update it to look something like
12:54
this so the changes here are you'll take
12:56
a save state handle into the constructor
12:58
of the view model
12:59
and then whenever you modify the counter
13:01
value you will write that to the save
13:03
state handle as well as mutating
13:05
whatever live data you might have
13:06
associated with it
13:08
and then when you want to initialize
13:09
things you can either read the value as
13:10
it was last saved
13:12
or you can create a live data that has
13:14
that value by using the get live data
13:16
function on the save state handle
13:19
if you don't want to use viewmodel or
13:21
save state library i do want to call out
13:23
that you can also use onsave
13:25
instancedate and this will also work
13:27
perfectly fine so if you have stronger
13:29
opinions about
13:30
architecture and you don't want to use
13:31
those libraries you can do that too
13:33
it will work just fine and at this point
13:36
we are handling everything now and
13:39
depending on where you are
13:40
you might have a lot of things that you
13:42
need to update if you're concerned about
13:44
these things
13:45
and at this point you might be thinking
13:47
well okay at least you're not going to
13:48
tell me anything
13:49
drastic like this is all doable but it's
13:51
so loud
13:53
and i have some bad news because you
13:56
also might be making assumptions about
13:57
your initialization logic
13:59
executing as you expect and this doesn't
14:02
always work and there's two ways that
14:04
i'm going to talk about this
14:06
the first one is your splash screen you
14:09
might have an activity that looks
14:10
something like this so when you launch
14:12
the application
14:13
you can show some screen that has a logo
14:15
associated with your application
14:17
and you can do whatever pre-fetching
14:19
work that you might need to do before
14:20
you can actually launch your ui
14:22
you'll then launch the activity for
14:24
whatever your main activity is to show
14:26
all the content
14:27
and then you don't want users getting
14:28
back to that splash activity
14:30
so you'll call finish on it to get it
14:32
out of the back stack
14:33
and at this point the initialization has
14:35
worked perfectly fine all of our data is
14:37
where it needs to be
14:38
the problem comes when we're being
14:40
restored from process depth
14:42
because when android is restoring our
14:44
application from process depth it only
14:46
restores activities that are on the back
14:48
stack
14:48
and because the splash screen isn't
14:50
there it means that any initialization
14:51
logic that we did there
14:53
has not happened this time and if you're
14:55
doing things like setting up singletons
14:57
or even like setting up dagger for
14:59
example in your splash screen for some
15:00
reason
15:01
then this can lead to a lot of
15:02
exceptions because your application
15:04
isn't in an initialized state that you
15:06
expect it to be
15:08
there's also another edge case that is
15:10
very bad
15:11
let's talk about your application class
15:14
a lot of people have initialization
15:16
logic especially for dagger
15:17
living in your oncreate method of an
15:19
application class
15:20
and there is an edge case very rare but
15:24
it can happen
15:25
where this never gets called if you look
15:28
in your bug tracker
15:29
you might actually see an exception
15:31
where your application can't be cast to
15:33
whatever type you specify
15:35
and if you're running into debugger and
15:36
when this happened you would actually
15:37
see that your application class isn't uh
15:40
isn't an instance of whatever
15:41
application class you specified it as
15:44
and this is going to be a really insane
15:46
thing to see and it seems very
15:48
impossible so the first thing that
15:50
you'll do of course is you'll go into
15:51
your android manifest and you'll say
15:53
no i did specify that the application
15:56
should be this class
15:57
and we need to talk more about what's
15:59
happening here to really understand the
16:01
full details
16:02
because if you look at what the
16:03
application class is you'll see that
16:05
it's the base class
16:06
and this happens to my knowledge in
16:08
exactly one situation
16:10
and it has to do with backups because if
16:12
your application is being backed up or
16:14
restored
16:15
then it gets initialized in sort of a
16:17
sandbox mode
16:19
the goal here is because there's a lot
16:20
of initialization that involves disk
16:22
writing it's very easy to initialize the
16:24
entire universe
16:26
your application is initialized in a
16:28
very basic mode
16:29
where the application class isn't used
16:31
it uses the base
16:32
instance instead your content providers
16:35
are not initialized
16:36
but your users can still launch
16:38
activities so if this happens
16:40
and your users go into your application
16:42
then you can actually
16:43
skip all of your activity or all of your
16:46
applications on create logic
16:48
and sometimes this will happen after
16:49
users have first downloaded the
16:50
application
16:51
so it's possible that the first
16:53
experience that they see is your
16:54
application crashing
16:56
this is very upsetting this is very bad
16:59
news to hear
17:00
and you might want me to tell you right
17:02
now how to fix this and
17:04
it's a little bit awkward so my first
17:07
advice here is
17:08
see if your users are affected by this
17:11
if you don't allow backup then this
17:13
probably isn't an issue for you
17:14
also if this doesn't affect that many
17:16
users then you probably don't want to
17:18
bother
17:19
but if this does affect users then there
17:21
isn't really that good of an answer to
17:23
this unfortunately
17:24
um you the the most clever thing that
17:26
i've seen somebody do to this
17:28
is they've in their initial activity
17:31
they've checked whether or not their
17:33
application is theirs and if it isn't
17:35
they just
17:35
show a dialogue that says we can't start
17:37
the application right now and then they
17:39
just close the application
17:40
and eventually they'll be able to start
17:42
it this does work
17:43
it is awkward and ideally the framework
17:46
would handle it for you
17:47
but this is really as good as you can
17:49
get with a lot of the initialization
17:50
logic
17:53
at this point we've made a lot of really
17:54
good progress here we've handled a lot
17:56
of different recreation and death
17:58
scenarios and even some really obscure
17:60
crashes
18:00
but because we're spending a lot of time
18:02
fixing these our
18:04
product manager is coming to us and
18:05
they're very upset because they need
18:07
features that's where the product that's
18:09
where the company is making money from
18:10
so we're going to talk again to our
18:12
designer because our designer wants us
18:13
to add a details page to some component
18:16
in our activity
18:17
in this case the example that i'm going
18:19
to go over is a list of students
18:21
so you might have some student list and
18:23
you want to open a student detail
18:24
fragment to see
18:25
all sorts of information about them this
18:27
is really common
18:28
one classic way of implementing this
18:30
navigation is to use a new instance
18:32
method that looks something like this
18:34
you can take your student you can
18:36
implement parcelable on it there's a
18:38
partialized annotation to do all that
18:39
implementation
18:40
for you which is really great if you
18:42
don't want to do it manually and you're
18:43
using the navigation library then
18:45
it will look something like this which
18:47
is also very clean
18:49
um the problem with using an argument it
18:52
applies to both of these
18:54
is really what we're passing in here so
18:57
in this case
18:57
the student object itself because
18:59
universities have a lot of information
19:01
about their students
19:02
it might be a very very large object
19:05
and it might be really big this can
19:07
cause a few issues especially if you're
19:09
assuming
19:10
that the student doesn't change between
19:12
the time that you start the new ui and
19:14
by the time
19:15
that you commit the changes you you
19:18
might assume
19:19
that the user that the student won't
19:22
change there
19:22
and you might not observe it which is up
19:24
to you to decide whether or not that's
19:25
good or bad
19:26
but really there's a couple of things
19:29
going on here
19:30
and the first one here is if you're not
19:33
using either of those two
19:34
implementations if you're not using
19:36
bundles or the navigation library you
19:38
might be making a mistake
19:39
where if you're using a property or a
19:41
constructor then
19:43
you might not be actually persisting
19:44
those values correctly and if you get a
19:46
configuration
19:47
change you might lose that parameter
19:48
entirely so that's its own issue if
19:50
you're not handling that
19:52
but the real issue that i want to call
19:53
out here is that there's more
19:55
restrictions than just the type of data
19:57
that we can fit inside of a bundle
19:59
so we know that there's limitations on
20:03
using primitive types parcelable
20:04
serializable and all of those
20:06
but right now we've got a very big
20:09
parcelable and there's actually a size
20:10
limitation
20:11
so we need to be mindful of that the
20:13
code that we've got is
20:14
okay-ish probably we're not going to run
20:17
into that limit
20:18
but if we don't if we start doing
20:19
something like this we're using
20:21
serializable that takes up more space
20:23
because
20:24
it's a less efficient persistence
20:26
mechanism but the worst possible thing
20:28
that you can do as far as size is
20:30
concerned is to use json instead
20:32
this is going to be very large and i do
20:34
not recommend that you do this
20:35
under virtually any circumstance because
20:39
there's a very small limit for how much
20:42
information can be stored inside of a
20:44
bundle
20:44
at most and this is a very theoretical
20:47
limit it's one megabyte
20:48
this is the amount of memory that
20:50
android can transfer between
20:52
processes which needs to happen because
20:54
this information needs to go from your
20:56
application
20:57
into the activity manager or for your
20:58
activities to be started and for the
20:60
state to be persistent
21:02
you also only get one bundle per
21:03
activity in terms of save state
21:05
so this does apply to both when you are
21:07
doing fragment arguments intent extras
21:10
and saved instance state bundles
21:11
um so with save instance say bundles in
21:13
particular you only get
21:15
one of these for a specific activity
21:17
which means that you need to be careful
21:18
about all of your fragments contributing
21:20
against this limit
21:21
and the official recommendation is to
21:23
keep all of this state under 50
21:25
kilobytes which
21:26
is not that large so the goal here is to
21:29
avoid complicated objects and don't use
21:31
json because it's
21:32
very easy to get like a three megabyte
21:34
json file which will never fit into one
21:37
of these bundles
21:39
in terms of saved instance state in
21:41
particular it might look something like
21:42
this this is extremely exaggerated
21:44
because there's more state than just the
21:46
state that you are persisting
21:48
there's also the internal state like
21:50
your view state your fragment backstack
21:51
and any fragment arguments that they
21:53
have
21:53
and for a single activity those all get
21:55
combined into a single bundle
21:57
so if you're using something like a
21:59
parcelable that will contribute a lot if
22:01
it's a very complicated object
22:03
but if you're using a json string that
22:04
can easily exceed this limit and you
22:06
don't want to do that
22:08
so instead of passing all those values
22:10
you should be very careful and mindful
22:12
about what you're
22:13
passing and persisting uh realistically
22:16
the type of information that you want to
22:18
be
22:18
storing either for arguments or for
22:21
savings and state is
22:22
state so it's things like user input
22:25
it's
22:25
anything that you can't derive later on
22:28
if you have an object that you want to
22:30
display
22:30
consider passing the id instead of the
22:32
entire object itself
22:34
and if you do need to pass the entire
22:36
object then definitely don't use json
22:38
use parcelable if you can so that's all
22:41
great we've now got this navigation
22:43
working
22:44
we've implemented that feature
22:45
everything is good and then our designer
22:47
wants us to
22:48
implement some retry because we might
22:50
have some errors that are network
22:51
related inside of our application
22:53
and we don't want to handle those
22:54
gracefully the first thing that you
22:56
might consider doing in this situation
22:58
is to use an alert dialog
22:60
the downside to an alert dialog is that
23:02
as soon as you rotate the phone
23:03
or do any other configuration change or
23:06
any other activity recreation
23:08
that dialogue is going to go away so
23:10
this isn't really going to work out
23:13
another thing that you might want to do
23:14
is you might want to use a dialogue
23:16
fragment but you might want to use
23:18
a really generic interface and i see a
23:20
lot of people trying to use lambda
23:22
expressions with dialogue fragments
23:24
the issue here is that there's not a way
23:26
to persist lambda expressions
23:29
so here i'm using put parcelable in
23:31
order to store this lambda expression
23:33
but that's very questionable because
23:34
lambda expressions aren't parcelable
23:37
if you actually were to try and do this
23:39
then it would be a compiler error in
23:40
that situation
23:42
or if you're using the navigation
23:43
library then you get one of two errors
23:45
the first one if you use the lambda type
23:48
is that it's
23:49
just not a legal identifier but the
23:52
other error that you might get
23:53
is runtime because it doesn't implement
23:55
either parcelable or serializable if you
23:57
pull out an interface instead
23:59
and really there's not a way to use
24:01
lambda expressions with fragments
24:03
effectively whatsoever
24:04
because the lambda expression can
24:06
capture any part of an activity
24:08
you might be trying to persist that
24:10
entire activity which at best
24:11
leaks the activity at worst does a whole
24:14
lot of other really nefarious things
24:16
behind the scenes
24:18
but really you're going to lose this
24:19
information as soon as you get a
24:20
configuration
24:21
change and you want to avoid it so
24:24
instead of going those routes you should
24:26
use a dialog fragment instead and you
24:28
can use a view model in order to send
24:30
those results
24:31
so here we've got the dialogue fragment
24:34
from before
24:34
we're getting an activity view model
24:36
specifically because if you get one
24:38
that's scoped to the fragment then it'll
24:40
go away after the dialogue fragment
24:41
disappears which you probably don't want
24:44
so here we're setting the positive
24:46
button to just retry some photo upload
24:48
that may have failed it may have caused
24:49
this dialogue to reappear
24:51
an alternative option if you don't want
24:53
to use a if you don't want to use a view
24:55
model is to use a callbacks interface
24:57
so here i've added an interface that has
24:59
an on retry upload function
25:01
and you can just require that the
25:02
activity implements this callbacks
25:04
interface and delegate the behavior
25:06
somewhere else and this is all fine and
25:08
you roll this out and it's
25:10
working and then you also notice that
25:12
your crash reporter has
25:14
illegal state exceptions complaining
25:15
about when you're showing this dialogue
25:18
and there's another assumption that's
25:20
made here about
25:22
being able to show fragments and do
25:24
transactions arbitrarily
25:25
and this also is not true
25:29
the reason or one reason that this might
25:31
not be working is
25:32
because in this case you might be
25:34
uploading a photo
25:35
and then suppose you get a call from
25:37
your mother and you're a good son or
25:39
daughter and you know that it's in your
25:40
best interest to answer this phone call
25:42
so go
25:42
you go ahead you answer the call you
25:44
have a lovely conversation
25:46
but in the background your photo is
25:48
still being uploaded your activity has
25:50
been suspended and it's still
25:52
doing that upload and it's still
25:53
churning along
25:55
if you get a network error in this state
25:57
and you are using that to show a
25:58
dialogue
25:59
then that's where the problem is the
26:01
problem here is
26:03
because your activity is in the stop
26:04
state at this point
26:06
you can no longer do fragment
26:08
transactions because there's a
26:09
possibility
26:10
that this activity gets evicted from
26:12
memory and
26:13
needs to be restored after process depth
26:16
and there's no way to modify the bundle
26:18
of save state that you've already
26:19
persisted
26:20
so the fragment manager makes it illegal
26:22
to do fragment transactions while you
26:24
are in the background for this reason
26:26
because you might not come back to them
26:28
i do also want to point out that this
26:29
isn't exclusive to phone calls if your
26:32
activity is in the stop state at all
26:33
then it's illegal to do fragment
26:35
transactions
26:36
and if you're using the navigation
26:37
library then you might not even know
26:39
that this is an issue because
26:41
when i was testing it on i believe a
26:43
beta version of the library
26:45
it actually swallows the error and just
26:47
logs into logcat
26:48
that were we can't do this navigation
26:50
call so
26:51
you don't get an exception and you also
26:53
don't get the navigation
26:54
but you don't get a crash which is good
26:58
if you really do want to show this
26:59
dialogue because it's important though
27:01
then
27:01
there's a few options the one that i've
27:04
seen a lot of people use
27:06
is to use commit allowing state loss
27:08
instead
27:09
the way that this works is it does allow
27:11
you to do the fragment transaction
27:13
while your activity is in the stop state
27:15
but it does mean that if your process
27:17
gets evicted and the user comes back to
27:19
it
27:19
then you might not see that dialogue
27:22
which
27:23
might be okay because that's a really
27:24
rare situation and with this dialogue in
27:27
particular it's just related to an error
27:29
so the user will probably see that their
27:30
photo isn't being uploaded anymore and
27:32
they'll be able to
27:33
make sense of what happened so
27:37
this is one approach to handling it if
27:38
you have a more
27:40
advanced dialogue where you do need to
27:41
make sure that it always shows
27:43
this can range from really difficult to
27:45
impossible and
27:46
you need to be really careful about how
27:48
you're thinking about how you do that
27:51
and at this point i've given you a lot
27:52
of information and pointed out a lot of
27:54
things
27:55
and you may be wondering well okay
27:58
can i go ahead and fix those right now
28:00
please
28:01
um and you may also be wondering how do
28:04
i do that
28:05
and really the advice that i have here
28:07
is to do the right thing
28:09
for your application and i will leave
28:12
you with one
28:12
parting assumption which is that you
28:14
have to fix every single one of these
28:16
problems
28:17
which might not be the case for your
28:18
application there's a few exceptions to
28:20
this
28:22
for example if you're not publishing
28:23
your application or if you have an
28:25
application that you haven't uploaded
28:26
that you haven't updated in seven years
28:28
and it's designed for like uh
28:29
like android 2.3 it probably doesn't
28:32
matter if you've had these problems
28:34
anymore
28:34
at this point there's not really anybody
28:36
using your application and
28:38
it probably doesn't matter that much
28:39
anymore likewise if you're doing a demo
28:42
library or you have some other toy
28:43
application just to demonstrate some
28:45
functionality
28:46
and that functionality doesn't rely on
28:48
process depth or configuration changes
28:51
it might not matter if you're not
28:52
handling these
28:55
another really big reason that i see
28:56
people not handling these which actually
28:58
works out ironically well is if they're
29:00
running on controlled hardware
29:02
so what i mean by that is if you are
29:04
building something for a kiosk or like a
29:06
smart refrigerator or something and you
29:08
are running your software on that
29:10
then you probably know that you're not
29:12
going to have to worry about
29:13
rotating from portrait to landscape like
29:15
if your refrigerator is running in
29:17
landscape all of a sudden
29:18
you have a very big problem and it's not
29:20
that you lost activity state
29:22
another big reason that you might not
29:24
need to handle this is if you're doing
29:26
something with a game engine for example
29:27
so if you're working with like unity or
29:29
some other 3d engine
29:31
you don't want to really recreate your
29:33
entire activity because that can
29:34
introduce
29:35
latency if you are in some really
29:38
intensive situation and you do want to
29:40
handle orientation changes
29:41
so you might use config changes if you
29:43
are a game engine to prevent
29:45
a scenario where your game stutters for
29:47
a second and then comes back to life
29:49
there's a few other exceptions that you
29:51
can think of to justify these behaviors
29:52
but if these aren't you
29:54
then please proceed
29:57
first of all please embrace activity and
29:59
fragment transactions
30:00
they're a reality they're going to
30:02
happen at some point or another
30:04
especially if you have a lot of users
30:05
that have downloaded your app from the
30:06
play store
30:08
and you can effectively think that an
30:10
activity recreation can happen
30:11
virtually any time to fix these or
30:14
really to
30:15
implement these correctly use view model
30:17
and save state or
30:18
override on save instance date and use
30:21
that saved instance a bundle to
30:22
maintain this state you can avoid uh try
30:25
to avoid android configuration changes
30:27
but there are a couple of exceptions to
30:29
this rule as well
30:30
for example webview does not support
30:32
writing its state into a bundle it used
30:35
to but that was actually leaking disk
30:36
space
30:37
so in all modern versions of android
30:39
that's disabled
30:40
so if you're using webview and you want
30:42
to support orientation changes
30:44
you might add config changes for window
30:46
size in that example
30:47
and just let your activity restart and
30:49
lose state if you change languages or
30:50
something
30:52
likewise if you're doing something with
30:53
surface view so something like a game
30:55
engine or possibly using the
30:56
camera you might not want to recreate
30:59
your activity because that can introduce
31:00
latency and stuttering which you might
31:02
want to avoid
31:03
so there are a few exceptions to when
31:05
you can use config changes but please
31:07
don't throw it on every single activity
31:08
in your application because that's
31:10
not a good idea the other thing to be
31:13
mindful of
31:14
and to tolerate is process eviction so
31:16
make sure
31:17
that any initialization logic that you
31:19
have on your splash screen
31:21
isn't critical for your application so
31:23
don't do things that will lead to things
31:25
like no pointer exceptions or
31:27
illegal state exceptions do lazy
31:29
initialization if you can
31:31
also assume that if you are in the
31:33
middle of a complicated flow
31:35
um there might be a situation where the
31:38
user leaves your app and comes back to
31:39
it
31:40
and all of the state that existed in
31:42
singleton's has been lost
31:43
so you need to make sure that anything
31:45
from your complicated flow is being
31:47
saved somewhere like a safe safe handle
31:48
or
31:49
in a savings state bundle um and
31:51
likewise
31:52
uh prefer lazy initialization lazy
31:54
loading if it's possible to do
31:56
a lot of the time this isn't possible to
31:58
do with things like frameworks like bug
31:60
reporters and other third-party apis
32:02
that you might be integrating with
32:03
and for those it's safe enough to leave
32:05
those in your application class and
32:07
if you really need to tolerate the
32:08
backup situation to do that on a
32:10
case-by-case basis
32:13
the last words of wisdom that i have are
32:14
to make sure that you are testing these
32:16
scenarios to make sure that you are
32:18
doing what is right for your application
32:20
if you have your app locked to portrait
32:22
trigger uncommon activity recreation
32:24
scenarios
32:25
do things like change your display size
32:27
change your language change your locale
32:29
um if you like trigger multi window run
32:31
it on a foldable phone make sure
32:33
everything is working
32:35
um trigger process that's using android
32:37
studio or using the venom library
32:39
and don't rely on the don't keep
32:41
activities toggle on the developer
32:43
options to test this for you
32:45
because it's not a real world
32:46
representation of what actually happens
32:48
don't keep activities will destroy
32:50
activities as soon as the user leaves
32:52
them
32:52
which isn't true if your activity is on
32:54
the back stack it will be in the stop
32:56
state it won't be destroyed yet
32:58
don't keep activities also does nothing
33:00
to simulate process death so be very
33:02
mindful about that if you've been using
33:04
it to test
33:05
process or to test activity recreation
33:07
scenarios in your application
33:10
and again i cannot stress this enough
33:11
fix the problems that affect
33:13
your application and your users because
33:15
there is not a one-size-fits-all
33:17
solution for
33:18
these issues thank you so much for
33:21
listening i've been andrew bailey
33:22
you can find all this information i'll
33:24
have a link to this recording as well
33:26
at andrewbailey.dev falsits where i've
33:29
got right now some additional
33:30
information a link to that venom library
33:33
and also some more resources if you want
33:35
to
33:35
investigate these crashes for yourself
33:38
and now i believe we can turn it over to
33:40
q a
33:42
yes we can thank you so much for the
33:44
amazing talk and who i sure learned a
33:47
lot
33:48
we have a couple of questions and i'm
33:50
gonna hand it over
33:51
to jan so we can get those
33:55
right away okay just give me one second
33:58
here
34:06
so jan should be able to present his
34:08
screener
34:10
and there we go
34:18
all right uh let's see going top to
34:21
bottom uh
34:22
is it also possible to use adb to kill
34:24
and recreate app
34:25
processes you probably can
34:28
i'm not sure what that would look like
34:30
but i'm guessing what it would be
34:32
is you would run the application leave
34:35
it by hitting the home button to make
34:36
sure that you're saving the instance
34:38
state
34:38
and then use adb to kill the process you
34:41
might also be able to use some task
34:42
killers in order to accomplish that
34:46
uh let's see uh is don't keep activities
34:49
in the developer options a good tool to
34:51
test
34:51
these state changes uh i think i just
34:54
mentioned that but it will not do
34:56
process it it won't trigger that
34:58
whatsoever it's not
34:58
attempting to do that uh will you share
35:02
your slides um i'm not planning to
35:05
upload the slides but you can re-watch
35:07
this presentation again i do also have a
35:11
cheat sheet that does a really good job
35:12
of summarizing all this information
35:14
on that andrew bailey.debt falsehood
35:16
site so
35:17
check that out i think that'll be really
35:19
helpful
35:21
what is the best way to test these
35:23
various configuration changes preferably
35:25
using
35:25
espresso or ui automator um that's a
35:28
really great question
35:31
myself oh i think myself um
35:35
so one thing that you can do is
35:38
you can use there's a testing library
35:40
called activity scenario
35:41
you can actually use that to trigger
35:43
entire recreations of
35:45
your application or of your activity
35:46
rather so if you
35:48
wanted to do that you can do that and
35:50
that would simulate configuration
35:52
changes
35:53
another option is you can literally
35:55
rotate the phone you can change the
35:57
requested orientation of the activity
35:58
which will also trigger configuration
36:00
change
36:03
when the process is destroyed and
36:04
recreated does the android platform
36:06
maintain the process activity as
36:08
flashback stack
36:10
yeah the backstack records themselves
36:13
are all maintained
36:14
activity manager saves those out to disk
36:16
um so the back stack itself is persisted
36:19
that's where those save instance say
36:20
bundles live
36:22
but nothing from your application
36:23
process at that point could be alive
36:25
anymore which implementation do you
36:29
recommend to prevent these issues if you
36:31
are using
36:32
mvp um oh that's a great question i
36:35
don't normally i don't personally use
36:37
mvp i use mvvm and i use viewmodel
36:40
personally
36:41
my recommendation would be to if you're
36:44
not using the model library
36:46
would be to write some sort of
36:48
abstraction or some sort of method that
36:50
will expose the saved instance state
36:52
bundle and then
36:53
override on save instant state delegate
36:56
that over to
36:57
your i guess presenter probably
37:00
and use that to write into that bundle
37:04
and then save
37:05
the state what happens if the bundle
37:08
limit size is exceeded i'm so glad you
37:10
asked i forgot to mention this
37:11
during the talk um it depends on os
37:14
version
37:15
if you're running on android 7 or higher
37:17
then you will get
37:18
a transaction to large exception and
37:21
that will get thrown at the time
37:23
of when you start the activity or save
37:26
your instance date it doesn't happen
37:28
when you exceed the size of the bundle
37:30
itself
37:30
the bundle doesn't actually have a size
37:32
limitation if you're running on an older
37:34
version of android pre 7
37:36
then you'll only get a log that goes out
37:38
to the console and i believe that the
37:40
activity just doesn't start which is
37:41
very
37:42
confusing and unexpected clap thank you
37:45
so much
37:48
uh let's see what happens if the bundle
37:51
limit size we talked about that
37:53
uh why can you use lambdas in material
37:55
alert dialogue big
37:57
builder does it suffer from this issue
37:59
too i haven't used material alert dialog
38:01
builder itself but i'm guessing that
38:03
this is
38:04
more attuned to alert dialogue i'm
38:07
guessing
38:07
that if you show this and then rotate
38:09
the phone that dialog will disappear
38:11
you can use lambda expressions with
38:13
alert dialogue and with
38:15
a material alert or some other dialogue
38:17
itself but as soon as you pass those
38:19
into a fragment
38:20
that's where you will get issues
38:24
what is the best way to mimic a
38:26
foreground service being killed by the
38:28
operating system
38:29
and how do you make sure to restart it
38:31
every time
38:32
uh let's see foreground services i'm not
38:34
too familiar on
38:36
how you could trigger death from them
38:39
especially because
38:40
from my experience the operating system
38:42
can seemingly decide to kill those
38:44
at random especially on poorly behaving
38:46
devices like samsung with a lot of
38:47
background restrictions
38:49
um so i don't really have too much
38:51
advice for
38:52
that i'm guessing you could probably
38:54
just
38:55
terminate your process if you look at
38:57
this process id
38:58
and you can send that basically
38:60
implementing a task killer if you wanted
39:01
to do that from
39:02
a testing scenario
39:06
can you describe why we should not use
39:09
live data inside repository in mvvm or
39:13
mvi architecture um
39:18
let's see live data itself in a
39:19
repository i don't disagree with
39:21
um the the main thing to be concerned
39:23
about is if you are
39:25
using live data inside of a repository
39:28
you
39:29
might be you might have state there that
39:31
doesn't
39:32
get persistent um so if you're relying
39:35
on
39:35
a repository to be in a particular state
39:39
then that might not be true because when
39:40
you're coming back from process death
39:42
those repositories will get
39:43
re-initialized and won't contain their
39:45
values
39:46
anymore um so that's the only reason why
39:49
you wouldn't want to do that otherwise
39:52
i'm not really trying to get an opinion
39:53
on like live data versus rx java code
39:55
routines inside of your repository
39:56
do whatever is right for you
40:00
let's see you spoke about testing
40:02
activity recreation
40:04
is there any reasonable way to test
40:07
process death to my knowledge
40:10
i'm not aware of a way of like unit
40:13
testing process death
40:14
it gets very complicated because there's
40:16
a
40:17
sort of intricate relationship between
40:19
the
40:20
test runner itself and your application
40:23
process um
40:24
i i would love to like to look into this
40:27
more because i'm
40:28
thinking that because of a side project
40:30
that i'm working on this will be
40:31
relevant
40:32
um but i don't know of a good way of
40:34
testing this
40:35
apart from doing that manually if you
40:37
are doing that manually then run through
40:39
the steps in either android studio or
40:41
use venom or another library that can
40:43
trigger process it for you
40:47
the practical bundle size limit is 500
40:49
kilobytes
40:50
i'm pretty sure it's 50 kilobytes um i
40:53
could be wrong on that but
40:54
the the link that i have the andrew
40:56
bailey.dev slash falsehoods i do have a
40:58
link to where google is
40:60
suggesting that you keep your state
41:01
under 50 kilobytes so that is an
41:03
official recommendation from google
41:07
uh let's see transaction two large
41:08
exception we talked about that
41:10
uh before making the fragment
41:12
transaction how can i check if the state
41:14
is safe oh that's another great question
41:15
because i've seen people do this as well
41:18
um i don't remember if there's an api
41:20
that exists
41:21
in the activity library or in the
41:24
activity framework that will do this for
41:26
you
41:26
um what i've seen people do in the past
41:28
is to keep it variable
41:30
and to basically say uh like
41:33
is instant date saved set it to
41:36
uh let's see set it to like true
41:39
initially
41:40
set it to false when you are on creating
41:43
your activity because
41:44
that savings and state is being restored
41:46
out and then
41:47
it again when you are writing that
41:50
instance state out and basically keeping
41:51
track of it manually
41:52
i wish there was a better way of doing
41:54
that but i'm not sure
41:56
if there is off the top of my head
41:60
um some other scenarios where unsaved
42:02
instance crash
42:03
can occur um i'm not sure what this
42:06
question
42:07
is getting at um i definitely covered
42:10
the main
42:10
situations for crashes um
42:14
i think there's a lot of other reasons
42:15
like this barely scratches the surface
42:17
and there's a lot of other ways to do
42:19
things incorrectly
42:20
um so if you're not doing best practices
42:24
then you could be running into your own
42:25
crashes that i haven't
42:26
seen if you're doing some more obscure
42:28
implementations
42:30
but sure there's all sorts of other
42:31
reasons why crashes could occur that are
42:33
related to saving instant states
droidcon News
Tech Showcases, Developer Resources & Partners
EmployerBrandingHeader
jobs.droidcon.com
![]() Latest Android Jobs
Kotlin Weekly
![]() Your weekly dose of Kotlin
ProAndroidDev
![]() Android Tech Blogs, Case Studies and Step-by-Step Coding
Zalando
![]() Meet one of Berlin's top employers
Academy for App Success
![]() Google Play resources tailored for the global droidcon community |
Droidcon is a registered trademark of Mobile Seasons GmbH Copyright © 2020. All rights reserved.