An Opinionated Guide to Dependency Injection on Android (Android Dev Summit ’19)

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]

Leave a Reply

Your email address will not be published. Required fields are marked *