[MUSIC PLAYING]
MANUEL VIVO: I am Manuel Vivo, and I
work in the Android Developer Relations team, which means--
DANIEL SANTIAGO: I'm Daniel Santiago.
I work on the Android Toolkit team.
MANUEL VIVO: I'm so excited to talk about Dependency Injection
today.
We know it's an important topic for the community with the way
it affects your application.
So we'll start with a quick introduction
to DI, and also the [INAUDIBLE] it has to your application,
so that everyone is on the same page.
Then obviously, we will talk about Dagger and other stuff
that I don't want to spoil.
Classes often require references to other classes
to complete some work.
And those references are called dependencies.
And every single application has to deal with dependencies one
way or another.
So as we can see in the diagram, the car cannot work without
an engine.
So we can say that engine is a dependency of car,
or that car depends on engine.
And with dependency injection, those dependencies
are provided to a class instead of the class
creating them itself.
So instead of car building its own instance of engine,
engine will be provided to car-- for example, at creation time.
If we see this with some code, you
will see that car here has the responsibility
to build its own instance of engine.
But not only that-- apart from that responsibility,
it has to know how to configure it.
And that might be too much for the class to do.
And so with dependency injection,
that responsibility is taken away from the class.
And now that parameter is passed as a dependency,
and so car can keep small and focused-- for example,
managing its state and not having to worry about all
of that.
And we believe that you should always
apply dependency injection principles to your application.
It doesn't matter how you do it, but doing it
sets the fundamentals for a testable and scalable
application.
Why is dependency injection that important?
First, it helps you reusing code and decoupling dependencies.
Classes no longer control how the dependencies are created,
and therefore they can work with any configuration.
And so here we can see that by passing
in different implementations of engine, we can reuse car.
That said, we don't have to do anything else.
We don't have to change the source code.
It is also provides a good balance
of loosely coupled dependencies--
a better balance than other alternatives can provide.
For example, with a service locator,
those dependencies are a bit more loose than with DI.
And it also helps you refactoring your code.
Those dependencies now become the API surface of our class
until their creation time or compile time,
instead of being hidden as an implementation detail.
Therefore, it keeps you with smaller and more focused
classes.
So if we see in an example here, we
have car with very, very long source code.
And when you see that your class is doing more than it should,
probably you can extract that logic out of the class,
put it in a different class, and pass it as a dependency.
For example, with engine-related work, we can create class--
the class engine-- and then pass that in as a dependency.
Now car reduces its scope, and now it's simpler.
And the cognitive load to work with class is a bit lower.
This is what is called "single responsibility principle."
But if we keep iterating on this--
when it makes sense, of course--
you will see that now car becomes simpler to manage,
and now we have smaller source code,
and we're just focusing on what car should do.
For example, assembling few parts, making it simple.
So if we want to [INAUDIBLE] this,
instead of having this massive class,
you can split it out in different parts,
and so it's simpler to manage.
And then also multiple people can
work at the same time on the app without conflicts.
DI also helps you with testing.
Now you can swap in different implementations of a dependency
to test all the different scenarios that you want.
So for example, here we have a happy path,
and we just want to pass in a fake engine just
to check that car works OK.
Now, if you want to check how car works with a failing
engine, you just have to pass a different instance
with an implementation of engine.
For example, this fake failing engine.
And that's it-- you don't have to do anything else.
And as I said before, it doesn't matter how
you do dependency injection.
However, we have some recommendations to give you.
Before showing anything, I would say that these recommendations
are relative, and you have to use your own judgment
because every application is different.
Now, we think that for medium and large projects,
you should use Dagger, which is our dependency injection
library.
And this is because Dagger is built to scale,
and it scales better than any other alternatives.
For small applications, it doesn't really
matter what you do.
So you can use a manual DI implementation,
and you can use a service locator pattern,
or you can even use Dagger.
The truth is that the sooner you add Dagger to your application,
the better it will be and the less you we have
to refactor it in the future.
If we see a representation of this with our graph,
and we compare the cost of managing your dependencies
with your application size, you will
see that manual dependency injection starts at zero cost.
But it grows exponentially when the app gets bigger.
Manual dependency injection is very important,
because if you try it yourself, you will see all the benefits
that DI can provide you.
But when you start adding that to a bigger application,
you will see a lot of boilerplate code
and a lot of stuff you have to manage yourself.
A service locator instead, it starts with a small cost,
but then there is a steep linear slope when the app gets bigger.
And by the end of that, you will have
kind of the same problems with manual DI.
So you will have boilerplate code--
not that good.
On the other hand, Dagger has a higher starting cost--
it takes a while to set it up.
But when the app gets bigger, it kind of plateaus, and then
that maintenance cost that you would
have with the other alternatives,
you wouldn't have that with Dagger.
So for those who don't know what Dagger is,
Dagger is a dependency injection library
that helps you manage the dependencies for you.
So it is going to generate the code that you would
have written by hand otherwise.
And that generation of code happens at build time,
so it is performant and safe to use in production.
So you are going to avoid those runtime sort of prices,
and it's going to provide you correctness at build time.
So in case it wasn't clear, we want you to use Dagger.
[LAUGHING]
And another benefit I didn't say is that Dagger
doesn't use reflection.
Even though reflection got faster in Android
over the years, not using reflection is even better.
But yeah, actually, we want you to use Dagger.
So it's our recommended tool.
And we think it's the best framework out there
to do dependency injection because of its correctness,
performance, and scalability.
And we know that because we use it in production.
We use it in our apps, like Gmail, Photos, and YouTube.
You can see how big those applications are,
and how much they can scale.
However, we know that Dagger and dependency injection
are complex topics, and they have a steep learning curve.
But we want you to help--
we want to help you in this journey to learn all of this.
Well, how are we helping?
We just released a set of documentation
to better help you understand dependency injection
and Dagger, from the basics to the most complex topics.
You can see the documentation in that link up there,
and it assumes nothing.
It starts from scratch.
It's going to explain everything assuming that you know nothing.
So I recommend checking it out.
And why are we doing this?
What we want to have is a common ground for everyone
that wants to learn dependency injection,
and wants to use Dagger in applications.
[INAUDIBLE] this-- if you want to do that, you might have two
or three different sources, different samples,
different blog posts.
All of them use a different set-up, and all of them
use Dagger in a different way, so it is very difficult
to understand all the topics and relate them together.
And so that's why we want to have a common guidance,
a common ground.
And we're trying to help both beginners and more experienced
users by giving good practices.
So just to give you a sneak peek of what's going on,
you will see that the documentation
is full of best practices, diagrams, and codes--
everything you would expect.
But that's not it.
We also released a new Codelab.
We believe that the best way you have to understand or to learn
a topic is with hands-on code.
And so we released this Codelabs called
"Using Dagger in your Android Application,"
where by the end of the Codelab, you will build an application
graph like this.
And it starts like any other application.
It has an initial manual dependency injection
implementation that you will refactor
to build something like this.
What about dagger-android?
dagger-android is a library built
on top of Dagger that reduces the boilerplate code when
using Dagger in Android framework classes,
such as activities or fragments.
It was an attempt on our part to make Dagger simpler in Android,
but it didn't work.
We heard from you--
[NERVOUS LAUGH]
[LAUGHING]
--we heard from you, and it doesn't solve the problems
that you really have in your daily jobs.
So we know we can do better, and so we
are stopping its development.
We are not adding any more features to do that,
because we think we can do better,
and we can have other ways to simplify Dagger.
And so we are trying to reduce the amount of code
you have to write, and we are really
making some improvements.
Danny is going to tell you more about it.
DANIEL SANTIAGO: Thank you.
So one of the most immediate improvements we are doing
is actually making Dagger work better with Kotlin.
And I'm always impressed with the Android community-- that's
you all-- because this was something
that started in the community, and we took it in.
And in a future version of Dagger,
you'll be able to use Dagger's API in a more idiomatic way
with Kotlin.
So this was on you, so thank you.
To show you what exactly we mean,
you'll be able to use object classes with module.
You could do this already using JvmStatic, but you shouldn't.
You should be able to just not need JvmStatic, which
creates this extra method.
Dagger will be able to understand this.
Other issues Dagger had with Kotlin was qualifier notation.
It was like hit or miss situation.
You would add a qualifier to a property--
it would end up on the getter, and Dagger
wouldn't understand it.
But similarly, we're trying to fix that.
That should just work.
And on a similar kind of subject,
Kotlin wildcards also confused Dagger a lot.
This is a part of this problem to tackle,
but ideally, you wouldn't need that anymore.
So keep a lookout on a future Dagger version near you.
It'll have some of these improvements,
and we're still working on some others, like companion objects.
Now, on the longer-term approach,
I'm super happy to announce that we're actually joining efforts
with the Dagger team to create what we think is a better
and simpler DI approach.
It's still under construction.
We're still working on this, and there's no library to try out.
But I'll show you how we're trying
to solve this, and some of the ideas,
and more or less how it'll look like.
We think we can make DI with Dagger simpler in Android
by really letting you focus on your dependency declaration,
and specifically taking away that setup process.
You really set up your components once,
and then you kind of like don't touch them again in a while.
Let's walk through some code to illustrate what I mean.
In Dagger, you usually declare your dependencies in modules,
you create a function.
And this is a pretty simple one.
All we're trying to say here is that for every player
that we request somewhere--
Player is an interface-- we want to provide an implementation--
the PlayerImpl.
Everything that you write in this function
is important-- that @Binds annotation,
the parameter, the return type.
It tells Dagger the type of definition
that you're declaring, the type of binding.
But that's a simple one.
Some can get pretty complicated--
qualifier annotation, multiple parameters,
a body with more configuration.
But the truth is that we really believe that everything
that you write there has a meaning and it's important.
So the time you spent working with DI,
that development time should really
go towards those definitions, because that's what you keep
working on as your app scales.
Sadly, that's not the only important part.
The truth is there's also the injection points.
This is usually your activities, services, fragments.
And usually with Dagger, you use the JSR annotation
to signify that you want a property to be injected.
But you also have to do some extra work.
You have to create this component,
probably grab it from your application context,
do memory injection.
The reason this has to be done like this
is because there's no construction injection
in your activities or services, at least not on API 29
and below.
Yeah, so that's not--
[LAUGHING]
Mmm-- yeah.
But if you take API 29, that's not a viable solution
if you want to support older devices.
But we kind of want you--
we want to burn that away.
We really want you to not have to deal with this setup.
It should be enough for you to tell us
that you want this Android component to be an entry
point into your graph, for its dependency
to be what starts getting everything from the graph.
Not only do we want to support those dependencies that you
define, but we also want to provide easy hookups
to other more lifecycle related things, like view model.
So this should be pretty seamless to do, too.
So kind of to summarize, those entry points
are definitely important, but we feel
like you shouldn't have to work too much towards them.
It would be enough for you to tell us, hey, I
want these Android components to be injectable--
please do whatever you need to do to make that happen.
So if you go back to how you currently do things,
you have to define components with Dagger,
you define your modules in the component annotation.
And then the entry point, we saw that we can represent that
differently.
But what happened with those modules?
How do we link those together?
Well, if we go back to a module, we
feel it might be just enough for you to tell us,
hey, I have this module that provides bindings,
and they can go into component.
The thing here is that these components will be predefined.
Everybody needs a singleton component,
and everybody defines it in a different way,
so why isn't there one out of the box?
Well, we feel we can do that for you.
And not just for singleton component,
but for other types of lifecycle related components,
like a [? TVT ?] or service.
So in a way, we can again take away that setup from you,
and you can just focus on your bindings.
Now, most of what I mention is on the production side,
but one of the benefits of DI in Dagger is testing.
If you are doing unit tests, that's
pretty simple-- construction injection can get you
pretty far.
But when you're trying to do something more complicated,
like integration tests, you need a lot of work
to just have production versus testing dependencies.
If we look at a small example here,
we're trying to test again that player
that we have with an activity.
This is like a white box test, so we
want to see what happens when you interact with your player,
how your activity reacts.
The thing here is that Player can be pretty complicated
to build, and there's no reason why your production bindings
definition cannot also work for your testing.
But this is somewhat sometimes hard to do.
We feel we can solve this if we provide you
with some test utilities that will create
a component for each test so that you can
get those bindings available.
But not only that, the true thing about DI
is that you can change part of your state
so you can further configure your test and test new cases.
But to do that with DI, usually replace your dependency
with fake.
But this is way harder to do integration tests.
But we also think there should be
a way where you can define test modules that will simply
swap parts of your graph only for tests.
So kind of to summarize, we feel we can provide you a simpler DI
approach by, again, letting you focus
on those binding definition.
We could have module discovery, which could
be great for libraries too.
Predefined components so you don't
have to worry about setting those up
or to do memory injection.
And the lifecycle of them, we can improve the testing story
with some test utilities, and simply
a better way to do test configurations.
This wouldn't be a closed thing.
We would actually provide APIs.
So if you really need to do some custom component,
we will have APIs for that.
So if you want to fall back to your hardcore Dagger,
you can do that.
This is our Jetpack dependency injection initiative.
It'll be integrated-- it's built on top of Dagger-- integrated
with Dagger.
We're actually working with the Dagger team.
It's obviously more opinionated in the sense
that we have those predefined components,
and you change a bit how you do things.
Because I work on the Jetpack thing,
we want to provide you also Jetpack extensions.
And these are basically abilities to construct
your fragment, have those multi-bindings for view model,
and even manage your workers.
So basically simpler integration with the rest
of the [? R ?] components library.
So you're leaving this room-- what should you take away?
Well, please use DI.
We feel that the value that it brings you is pretty good.
It creates good patterns that can scale.
Give it a shot at Dagger.
And we know it's complicated, so we
have some guidance around it.
And overall, we're improving DI in Android, so stay tuned.
These are some good ideas, and we want your feedback on it.
So come find us in the sandbox, talk to us.
And hopefully, we can shape a better future for DI.
Thank you.
[MUSIC PLAYING]
[APPLAUSE]