[MUSIC PLAYING] LUCAS BERGSTROM: Good morning. [APPLAUSE] It's great to see
everybody here. Hi, I'm Lukas Bergstrom,
Product Manager for Android Architecture Components. And before I get started,
I'm curious how many of you were here last year when
we announced Architecture Components for the first time? Awesome. OK, this is a much bigger
venue, so that's actually a decent percentage. When we did launch Architecture
Components last year, we were doing something
sort of different for us. For the first time, we were
offering explicit guidance in how to architect
your app and giving you components letting you do that. And frankly, for us it was
a little bit of a journey into the unknown. So 12 months in, let's
check in on how we're doing. So we've shipped 26 releases
since May of last year. So we've been
constantly iterating and improving the core set
of architecture components. In the sign of any healthy
open-source project, we have a very
active issue tracker. I'd like to thank
everybody that's taken time to file a feature
request or even a bug. And we've closed a lot of those. We launched a major new library,
Paging, which is now stable. And we'll talk a little
bit more about it today. And I'm pleased to say that
based on our survey data, over half of you are
using or planning to use Architecture Components. And this survey was
done only a few months after we went stable. So this data is pretty
out of date by now. But I'm pretty proud of the
fact that only a few months after launching to stable, over
half of the Android developers we talked to were planning
to use this stuff. But more importantly
than any of this, you've told us that
Architecture Components actually make it easier for you
to build robust apps. That having a clear path on
how to architect your app and components that help you
realize that has actually made a difference in
the real world in how you build your apps. And we've heard
that not just once, but we've heard
that over and over from a lot of developers that
have taken time to speak to us. So Architecture
Components has grown. And we're going to
continue to invest here. This is foundational, we think,
for Android apps going forward. But not just are we investing
in Architecture Components, but this year with
Jetpack, we're going to take that same approach
that we took with Architecture Components, a blank
sheet of paper approach to how Android developer
experience should be. And, you know, how we can
improve things for you. And Jetpack is going to
take that and apply that to the rest of the developer
API service that we offer. So now I'm going to turn
it over to you, Yigit, to talk about what's new
in Architecture Components. YIGIT BOYAR: Thanks, Lukas. [APPLAUSE] Thank you. So we all talk about
what we have been doing in this last one year. We all look at the
existing libraries, talk about the
improvements, and also look at the new shiny stuff, paging,
navigation, and work manager. Let's start with life cycles. So we shipped lifecycles
in the last I/O. But to better understand why
we have created this component, let's go to two years before. In 2016, we did a bunch
of developer surveys and asked developers,
what is the hardest part of Android development? And by far in the
list, big surprise, was lifecycle management. We're wondering,
like, what can be hard about a phone rotating or
user switching applications? This happens all
the time on Android. Android is built for this. But if you look at
the problem in detail, if you want to handle them
properly in your application, you need to understand these
state graphs very well. And when these two
are interleaved, it becomes very confusing. So we have created
lifecycles component to get rid of these problems. And it seems to be working
because many developers give us testimonials where
a group of problems just disappeared from
their applications when they started
using these libraries. Another important change for
lifecycles was one year ago we had introduced them
as an optional library. But now, they are a fundamental
part of Android development. You don't need to do any
additional libraries to start using them. Now both AppCompatActivity and
the Fragment class implement lifecycle when they're
out of the box. Another interesting
thing that happened was the community adoption. So we ourselves create new APIs
that works with lifecycles. But we know other
people are doing the same in their libraries. And this is so much easier
because already AppCompat has these dependencies
so you can easily depend on them in your libraries. One great example of this is the
AutoDispose library from Uber. So if you're using
RxJava, but you want the automatic
lifecycle management, you can just add this auto
disposable onto your stream, give it a lifecycle, and it will
manage the subscription for you for free. Now, working on these things we
also discovered more problems we have. One of them is the
Fragment view's lifecycle. Now, Fragment has a very
complicated lifecycle. Let's look at an example. So if you have a Fragment
that when it is created it stops observing
LiveData, it goes through the regular creation. We create a View for it. And it goes to the resume state. At this point, if you are
meanwhile using your LiveData, your UI will start
displaying them. Everything works fine. Later on, user hits
a button, so you want to detach this
Fragment because they are going to another Fragment. And you are going to
stop that Fragment. But once the
Fragment is stopped, we don't need the View for it. You would like to
reclaim those resources. So we destroy the View. Later on, user hits
the back button. You'll go back to the
previous Fragment. We reattach it. Now, because we have
destroyed the View, we need to create a new one. We go ahead and do it, but
this is a brand new View. It goes through the
regular creation cycle. And now we have a bug. This new View will never resume
the state of that LiveData because you are
using the Fragment lifecycle to observe it. So we don't have any reason
to redispatch the same value. Of course, if LiveData
receives any value, the UI will be updated. But it's kind of too late,
because on recreate, your UI has a bad state. Now, this left you
with two options. You would either
subscribe in onCreate. It looks very clean,
one time setup. But it will fail if
you recreate the View, so you needed to
manually update the View. Or you were subscribing
onCreateView, which handles recreation. But now you have
double subscriptions that you need to remove. You lose the automatic
lifecycle management. So the problem here
is that Fragments have not one but two lifecycles. And we have decided to embrace
it and give the Fragment View its own lifecycle. So now, starting with support
library 28, or Android Text Fragments 1.0, while
observing your LiveData, you can specify
the real lifecycle. So while you are observing,
if it is about the View, you use the View lifecycle. Otherwise, use the
Fragment lifecycle. And we manage the
subscription for you for free. OK, data binding--
so when we started to look at our offerings
as part of Jetpack, we had decided to move
data binding as part of Architecture Competence. But if you're not already
using data binding, data binding is our solution
for boilerplate free UIs. So if you have an object like
this in your application, in your binding layouts,
you can reference the fees in your object. And we take care of
updating the UI for you. In Data Binding 3.1, we
have added native support for LiveData. So if you're a view
model like this, which has a LiveData for
users, and if you pass it as a parameter to
your binding, you can use that LiveData as if it's
a regular field in your binding expressions. Data Binding will
understand that is LiveData and generate the correct code. Unfortunately,
this is not enough for it to observe it
because Data Binding doesn't have a lifecycle. To fix that, when you get
your binding instance, you just tell it which
lifecycle it should use. It will start observing the
LiveData to keep itself up to date. You don't need to write
any code for this. We have also rewritten parts
of the Data Binding compiler to be a lot more incremental. So if you have a project
with multiple modules, it's going to
compile a lot faster. We are also working on making
the compilation even more incremental. But they're not finished yet. This new compiler also
gave us the ability to support Instant Apps. Now you can use Data Binding
in the future modules of your Instant Apps. OK, Room, my favorite
Architecture Component. So Room is our solution
for object mapping that minds the gap between
the ASCII lite and your Java or coupling code. One important change
we did in Room 1.1 was the support for
better multithreading. So if you are
using one Room 1.0, and if you have one
thread that is trying to insert a lot of
data into the database, and you have another
thread that's trying to read data while
the write is executing, your read will be blocked. And it can only execute
after the write is complete. In Room 1.1 with writer logging,
they can run in parallel now. And another nice feature
of this thing, your writes run a lot faster than before. Best part of this
change, you don't need to do anything to
take advantage of this. If the device is running
Jelly Bean or newer, and it's not a
low memory device, we are going to enable
writer logging for free. OK, another important
addition to Room was the support for RawQuery. But to better understand
why we need RawQuery, let's talk about the
Query annotation. Now, when you are
using Room, you can specify your SQL
query in this annotation. You can use the name
bind parameters. You can pass those parameters
as regular function arguments. And you can tell the
Room what to return. The best part of this
setup is that Room is going to validate all
of this at compile time. So if there's a
mistake in your query, if your parameters
doesn't match, or what you try to return
doesn't make sense, it's going to fail
the compilation and let you know what is wrong. Now, this is all
cool except what if you don't know the
query at compile time? What if you're writing a
real estate application, my user can't search the
houses with their price? Maybe they want to specify
number of bedrooms, bathrooms, whether it has a yard. If you needed to
query in a method for each iteration of this, that
will be impossible to maintain. So what you do in
this case is you create the query at runtime
based on the options the user provided. And you prepare the
arguments for that query. Now you obtain an instance
of the Room database and use this query
method to get the result. So far so good. The problem with this approach
is that it returns your cursor. Who wants a cursor? You're trying to get
the list of houses. So this looks like a
failure of the library. Hence, we have decided to
introduce RawQuery annotation. It looks very similar to Query
except instead of specifying the query in the
annotation, you pass it as a parameter to the function. And then you tell us what
you want us to return. Now, here, Room cannot
validate that query anymore. So it's kind of, you promised
us to send the right query, and we'll take care of it. Once you have that, if you go
back to our previous example, we can get an
instance of our Dao. Now we need to
merge our parameters and the query in this simplest
guide query class, which is basic data holder. And then you can pass
it through the Dao and get the list of houses--
no more cursors, no more boiler plate code. OK, Paging-- so Paging is
our solution for lazy loading in RecyclerView. But to better understand why
we developed this component, let's go through an example. So you have a list like this. It's very common. Every single
application has this. User can scroll. But you know, it actually
represents a much larger list than what's on screen. And if you're an application
like Twitter, for instance, it's a really, really long list. So you probably cannot
fit it to memory, and it's also very
inefficient to log all of it. So what you will do is
you will keep some of it in memory, the rest
of it in database, and you also have your
server compromised where you pull this data from. Now, this is actually very
hard to implement properly. That's why we have
created the Paging Library to make
these common falls very easy and
efficient to implement. Paging Library comes with
a PagedList class, which is an actual Java
List implementation, but it works with a data source. Every time you access the
items in the PagedList, it pulls data from the
data source lazily. So if user scrolls,
you need more data, it just brings in more data. Now, let's look
at how we can get an instance of these classes. It's actually super easy
if you are using Room. So Room already denotes how
to create a data source. It's a great example of how
these Architecture Components work very well together. So you can just tell the Room
to return your data source or a data source factory. In this case, I'm using
a data source factory because data waves is
something that changes. And each data source
represents a snapshot. So we need a factory so that
we can create new data sources, run database changes. Once you have that, you can use
this Live Paged Builder class, pass the page size,
and code build on it. It's going to give you a
live data of pages of users. This is almost the same thing
as LiveData of list of users. Now, in your
activity or Fragment, you would use this
page to set up there, which is the
RecycleView adapter that works with PagedList. You would observe the
live data every time we have a new page list,
give it to the adaptor. And inside your
adapter, you can just call this get item function
to obtain the user object. This is a super
simple code to write. And we take care of all the
hard work of paging it lazily for you. Now, even though I have
shown all those examples with LiveData, paging supports
RxJava out of the box. So if you are using
RxJava and you want that observable
of pages, you can just get the same
factory that Room generates. But instead use the
Rx PagedList builder to build your
observable or flowable. Now, paging supports
paging from the databases I have shown here. But it also supports
paging from the network. Or you can combine both
database and the network for the best user experience. To learn more about it,
please join me and Chris Craik tomorrow at 2:30 in
the Paging session. All right, now about FOO. LUKAS BERGSTROM:
I think, possibly, some of the suspense about
what FOO is is gone now. But when you think
about core problems that almost every
app has to deal with, in-app navigation has to be
close to the top of the list there. Right now the framework doesn't
really offer anything for you to do or anything
for you to use there other than start activity,
which for various reasons is not the best option. So that means that
for navigation, there are a bunch of things that you
have to figure out on your own. And that ranges from executing
a Fragment transaction without throwing an
exception, hopefully. Passing arguments from place
to place, possibly even with type safety if you
can figure that out. Testing the
navigation is working and that the right things
are happening when you navigate from place to place. Making sure that the up and
back buttons work correctly and take the user where
they're supposed to go. Mapping deep links to
destinations in your app and having that work. And by the time you've
solved all of these problems, you've typically gone
one of two directions. You've either written 60%
of a navigation framework just for your app. Or you've got a lot of
error-prone boilerplate. So everywhere navigation
needs to happen, you've got a bunch of
parallel lines of code that need to be changed any
time the navigational structure of the app changes. And this is all pretty
brittle and can end up being a headache. And individually these
problems are pretty tractable. But when you compose them
into a real-world example-- so say I have an item
screened in my app, maybe a product screen. And that screen is
accessible via Deeplink. But actually, there
are other pages that if the user
had navigated here by opening the app
from the home screen, they would have come via the
home screen, the category screen. And I want the up button to
take them through those screens rather than exiting the app. So that means that if someone
deep links into the app, I need to synthesize
these screens and add them to the back
stack, but only on a deep link. And talking to a
third party developer, he said, you know,
it's when you're in the middle of writing code
to do this on any deep link into my app and synthesize
the back stack correctly, you start to feel
like maybe this is a failure of the framework. And so that's why we're
really happy to be launching navigation, which is both a
runtime component that performs navigation for you
and a visual tool that works with XML to define
the navigational structure of your app and then
allows you to just navigate at runtime with a
single navigate call. And so the kinds of
things that you're going to get for
free, that you simply need to define in XML and
then the navigation framework will handle at runtime
for you are animations, passing arguments in a typesafe
way from place to place, making sure that up and
back work correctly, and mapping deep links to
various screens in your app. And last but not least, no more
Fragment transactions ever. [APPLAUSE] So I'll show you a couple
demos of this in action. The first one is just to kind
of give you an idea of what this all is. So we're looking at a set
of Fragment destinations in my app. And I'm adding a new one. And now I'm creating an action. And this action is the
thing that I'm actually going to call at runtime
to go from place to place. And you can see that there
are a bunch of other options that we'll get into more
in the navigation talk. But the one thing I do want to
show you in more detail right now is the example we
went through before. So this is a simplified
version of that where there isn't a category screen. We just have the home screen
and then the item screen. But I'm going to, right
now, configure this to both have a deep link
pointing at the item screen and to make sure that if
someone deep links into the app, that they go to the
home screen first when they hit up or back rather
than just exiting the app right away. So first, I'm just going
to configure a deep link on this screen. And the curly brackets that
I'm going to put around item ID indicate that
I want to extract a variable there and
pass it as an argument into the item screen. OK, and now that's ready to go. And if I compile and run
my app, that I'll just work and navigate to
the right destination. Now, I just set the screen
to the start destination. That means that it's the
hierarchical parent of all the other screens in the graph. So when someone deep
links into the item screen and then hits up, they're
going to go directly to that home screen. So now I've just solved
in 30 seconds what would have been a really
terrible and time consuming task in Java or Kotlin
back in the old world. So now I'm going to
pass it off to Yigit to talk about Work Manager. YIGIT BOYAR: I've been using
navigation a couple of weeks for some demos. It feels like magic, so
I hope you all liked it. Our next Architecture
Component is Work Manager. Now, Work Manager
is our solution for deferable
guaranteed execution. What do I mean by this? You're out of actions on Android
that you really, really want to do if user does something. For instance, if user
decides to send a tweet, you want to send it now. But if there is no
network connection, you want to send it
as soon as device is connected to the internet. There's things
like uploading logs you may want to do if
the device is charging. Or you may want to
periodically sync your data with your backup. Now, we know this is not
a new problem on Android. And we had some
solutions for this. We have introduced
JobScheduler in Lollipop. And we have Firebase
JobDispatcher that back forced this
functionality in the devices which has Google Play services. And we also have AlarmManager
for exact timing. Now, each of these has different
behaviors and different APIs. It becomes very
hard to implement. Hence we have built WorkManager
that sits on top of them and provides a much cleaner
API with new functionalities. WorkManager has two
simple concepts. You have the workers that
execute these actions. And you have the work requests
which trigger these workers. Now, if you want to
look at a sample worker, this is basically all you do. You extend the worker class. You implement one function
that says do the work. And that function
just needs to return to us what happened as
a result of that work. So you can do whatever you
do, and you return the result. There is no services,
no intents, no bundles, nothing like that. Once we have the worker, we
need to create a work request. So you can use this
one time work builder. Or there's a periodic
version of this one. You can specify
the worker class. But now, you can
also add constraints. You can tell it only run if
there's network connection if the device is charging. Or you can specify
a back off criteria. So if the worker is failing,
how should we retry it? You can also pass input
parameters to these workers. Once you build
that work request, you can get an instance of
WorkManager and inquiries. Now, WorkManager will
take care of executing it. One of the important distinctive
features of WorkManager is that it has input
and output semantics. So your workers
can receive input, but they can also
output some data. You could observe this
data through WorkManager. But it was actually really
useful to chain your workers. So imagine you have an
application where user picks an image for their device. Now you want to run some image
processing on that picture. And then once it's
done, you want to upload it to your server. Now, these are two
different units of work. Like, you can process the image. Maybe you want to do it
when the device is idle. Or you can do it anytime. But to upload it to server,
you need internet connection. But you don't want to
wait the processing for the internet connection
because it doesn't need it. This is super easy to
implement in WorkManager. So we'll have two
different workers. They all have single
functionality. One of them does the
image processing. The other one does
the upload to server. OK, the helper function
that receives an image file and creates the process
image work request-- so it prepares the input,
just uses the same builder to produce the request. And now we get that. Now we wanted our
network upload to wait for the internet connection. So we set the constraint. We say, OK, wait for
internet connection before trying to run
this work request. And then create the upload image
work using that constraint. Once we have them, you
can tell WorkManager, OK, begin with the
process image work. Once you are done, then run
the upload to server work. And now you enqueue both of
these as an atomic operation to the WorkManager. Now your device can restart. Anything can happen in between. We will take care
of running the two. You can also use
this API extensively. Like, you could run
image processing in parallel the same way. So if user pick
multiple photos, you want to process all
of them, but upload it to server while
some of them are done. You can easily do
that with WorkManager. We'll just use the same function
or create three work requests for each of the
images user picked. We create the upload
work in much the same way we did before. And now we say, OK, begin with
all these three work items. Once all of them are done,
then run the upload work. And then you enqueue that
as an atomic operation. And it takes care of running it. Another important
feature of Work Manager is that it is not just a wrapper
for JobScheduler or Firebase JobDispatcher. It's actually an
executor itself. So let's look at why
opportunistic execution is important. So if you're an application
where user can send an email, so user hits the
send button, you send the job info
to the JobScheduler or to the Firebase
JobDispatcher. And it will eventually call
you to execute it back. The problem here
is that you don't know how long it will take. Even if you're device currently
has network connection, it may take a couple of
minutes for JobScheduler to call you back. And you have no control over it. You just don't know. And it results in a
bad user experience. To work around that what
you usually do is you also have your own thread pool. Whenever a user
hits send, you try to run the same
thing there as well. And you take care
of the duplicating when the JobScheduler
calls you back. If you are using
WorkManager, you don't need to think about it. Because when you send the work
request to the WorkManager, it puts it into
its own database. Then it tells
JobScheduler, or whichever scheduler it has on
the device, OK, I need to be invoked when
these constraints are met. But it also checks those
constraints itself. And if they're already
ready, it will instantly start executing the job. And later on, if JobScheduler
comes in and asks to execute, now WorkManager knows
whether it is executed or not and handles the
request properly. To learn more about
WorkManager, please join us today at 5:30 in
the WorkManager session. All right, what's next? LUKAS BERGSTROM:
OK, so I think it's been a pretty great year
in Android app development. Hopefully you agree. We launched the set of great
new components last year. And we kept working on those
and iterating on those. We've launched three new
major components, WorkManager, Navigation, and
Paging since then. So does that mean we're done? Obviously not. We have a lot more to do. And the first thing
that we want to do is we want to make Architecture
Components the default way that people build Android apps. And that doesn't mean that
it's going to be required. But it does mean
that we want to make sure that as many
people as possible get Architecture Components,
regardless of how they get into Android development. So that means that
not only are we going to be building more tools
like the Navigation Editor into Android studio that
are sort of Architectural Components aware, but we'll also
be adding more templates that include things like view
models so that people starting a new project have the easiest
possible OnRamp into Android development. And in terms of
libraries, not only are we going to be building
more Architecture Components, and not only are we
going to be building in more of the core
Architecture Components goodness like lifecycle
awareness into Jetpack, but we want to look at
other Google APIs as well and see how we can make those
Architecture Components aware. So that, for example, if
you're calling another Google API that's asynchronous, it
already has that built in. So that you're kind of getting
Architecture Components benefits, whether you know
you're using it or not. And then finally, we've
heard from everybody that you want us to speak
with a single voice. You want us to give clear
and consistent guidance. So that means that in
terms of education-- and that means not
just documentation. But it also means
sample apps, code labs, that all this stuff
is going to be refactored to kind
of be built on top of Architecture Components. So that whether you start with
the guide to app architecture or you just download
a sample MediaPlayer app and you start
customizing it, that you kind of, regardless of what your
on ramp in and development is, that we get you to the
best possible place. We know that we still
have some areas left to address in the core. And you can see
some of those here. So this is just to say that
we are definitely not going to stop investing in the
original set of Architecture Components. And there is some, not
just problem solving here, but some exciting stuff
that we can do around, how can we make Architecture
Components as idiomatic and fun to use as possible for
people using Kotlin? So there's a lot to be
done still in, I think, the core set of app architecture
and lifecycle problem areas. So we'll keep working there. But beyond that, I think
you'll see something interesting about
our trajectory if we look at all the components
we've launched to date. So last year, this set of
Architecture Components was-- these are relatively small
pieces, relatively small APIs that are designed to be used
in a lot of different places in your app. And then if you look at Room
navigation and WorkManager, these are much larger
and richer APIs. But they're still
relatively self-contained. They solve a single problem. They do it really well. Paging also solves a single
problem and solves it well. But in this case, we took a
very specific use case, so lazy loading for recycler view. And we're actually, in
this case, orchestrating multiple Architecture
Components and pieces of Jetpack to solve that problem. So Paging is a little
bit higher level. It's not just here's your
object mapping layer. It actually takes
a very specific, I know I have a recycler
view with more data that I can fit in memory. And it uses multiple pieces
of Architecture Components to make that as
easy as possible. And we want to continue to
build more stuff like that. So we're not just
going to keep investing in the core areas of app
architecture and lifecycle. But we want to start solving
higher level problems and make more and more
as easy as possible. But I can't leave today
without thanking everybody that helped us get here. The reason that we were able
to have a really high quality bar for Architecture
Components was because a lot of people,
many of whom are here today, were really generous
with their time. And that includes not
just filing issues on the Issue Tracker, but also
testing pre-release components, having one-on-one
conversations with us to tell us what your biggest
problem areas with Android app development were. This has been critical
to us in making sure that we're focusing
on the right problems and delivering solutions that
are going to work for you. So I really have
to thank everybody in the community
that's been so helpful. YIGIT BOYAR: Thank you. [APPLAUSE] [MUSIC PLAYING]
This was a problem I suffered with all Fragments and now it's fixed because the Fragment View has it's own lifecycle.
Here is detailed explanation of the problem: https://medium.com/@BladeCoder/architecture-components-pitfalls-part-1-9300dd969808
New solution in support lib 28:
The fact that this was ever a problem is even more evidence that Fragments are too complicated. We need something simpler.
So, there is one more lifecycle now and if you accidentally use the wrong one in wrong place then you'll have a subtle bug.
Very elegant solution.
When I gave this talk I knew that we're going down the rabbit hole of ever increased complexity and subtle bugs, but the reality exceeded my worst expectations.
What I especially dislike about IO 18 is how easily googlers talk about their huge mistakes. Like it could happen to anyone and it's not a big deal: "yeah, we introduced this bad library/API/practice/bug/etc, but you know, Android is difficult, haha".
Arch components, Loaders, Fragments, SyncAdapter, JobScheduler, AsyncTask, numerous Layouts, etc. They just joke about how all of them sucked, or were too complex, or contained serious bugs, or whatever.
Well, IMHO it's not funny at all.