droidcon NYC 2019
Tweet |
droidcon News
Dagger Party Tricks
By
Zac Sweers
droidcon New York City 2019
Dependency injection doesn't have to be all plumbing-doom and tedium-gloom. This lightning talk will cover some simple clever things you can do with Dagger to simplify modularization, improve startup perf, and hide your intermediate dependencies.
Warning: talk examples are heavily taco-based. Come hungry to learn.
Transcript
en-us
00:00
[Music]
00:12
my name is Zack Spears I work on a bunch
00:15
of open store stuff like ketchup or
00:18
mocchi or auto dispose I'm currently
00:21
funemployed and working on moving to New
00:23
York which I realized as a technically
00:24
fancy way of saying homeless and
00:26
unemployed and I'm learning some things
00:30
about New York
00:31
well I'm here like the exotic
00:33
pronunciations of words like Houston and
00:35
stiverson and I hope you learned
00:39
something here too so a quick feel for
00:41
the audience here who uses dagger
00:44
alright and that's that basically
00:47
everyone one two anyone used coin I
00:50
won't tell Jake alright
00:54
juice anyone here not used di in any
00:59
form I like how you got a tap on the
01:02
shoulder just to remind you to brace for
01:04
that so if you're not so for anyone who
01:07
doesn't use dagger to some of these work
01:09
and other di patterns to dagger just
01:11
makes it easier so back to the talk
01:14
today I'm gonna be talking about some
01:16
dagger party tricks specifically for
01:18
issue of them
01:20
these aren't meant to be like weird
01:22
trivia or gotchas or anything but rather
01:24
just clever and neat ways that you can
01:25
use a dagger to enable better patterns
01:28
in your code so the first thing I want
01:30
to talk about is bleeding on a strong
01:32
foot here something that I think you can
01:34
take home and do today it's really
01:37
simple and straightforward when I gave
01:40
this talk a few months back I posted
01:42
this to Twitter and basically you know
01:46
letting people know is gonna do it and
01:47
perhaps not surprisingly it turned into
01:49
a bit of a guessing game from some
01:50
people so some of the assets were good
01:53
you know like that fast in it or using
01:56
lazy or static providers one of these is
01:59
on the right track for this some of
02:01
these were on other tracks like make
02:04
everything's to go yeah pick everything
02:06
serializable and loaded start up or kill
02:11
the process on start up
02:13
so they're clever but these aren't what
02:17
I'm getting at the solution that I am
02:19
talking about though is clever but it's
02:21
nothing fancy so who your uses retrofit
02:25
all right who doesn't all right keep
02:29
your hands up anyone that sees them
02:31
befriend them and show them the light
02:33
there's that so here's the same like
02:38
three lines of retrofit that everyone
02:39
pasted into their app it's pretty
02:41
vanilla retrofit and somewhere later
02:42
you'll pass it an Rx to call adapter or
02:45
your master converter but for now we're
02:47
just gonna focus on the pretty basic
02:48
stuff here so chances are your plumbing
02:50
in your okay httpclient here and again
02:53
pretty standard farther up the chain
02:55
will have other provides for the clients
02:56
such as its cache and there you go your
02:59
standard square buffet so who's
03:01
basically got this in their app all
03:04
right cool so who does this at startup
03:10
all right seeing a lot of like shaky
03:12
hands like Queens wave here so that's
03:16
you know around boy I thought most
03:17
people are doing this one startup
03:18
because you got to start you know if you
03:20
log analytics when someone opens your
03:22
app then you're doing this so let's look
03:24
at this a bit in context though so one
03:26
app startup RDI graph is going to
03:27
roughly follow this path where our cash
03:29
is going to get initialized and then
03:31
we're going to go to ok CDP and then
03:34
finally we're gonna feed that into
03:35
retrofit all this gets injected
03:38
somewheres a constructor of our
03:41
imaginary class here and here's the
03:43
kicker this step can take a hundred to
03:46
150 milliseconds to fully start up
03:48
because you've got a lot going on here
03:50
like in OK HTTP there's this trust
03:52
manager Factory that on some devices can
03:54
take a hundred milliseconds in the wild
03:57
whenever you're providing your cache up
03:60
here this I thought I had laser pointer
04:05
for a second that was cool so yeah
04:09
whenever you're creating your cast
04:11
you're actually having to go look at the
04:12
cache directory here it used to be
04:15
initially eager or used to be eagerly
04:18
initialized in ok HTTP as well which
04:21
would then involve some disk i/o and
04:23
even now it still spins up its own
04:25
executor during
04:26
initialization and you're paying all
04:27
this cost up front for something that is
04:29
ultimately not needed maybe I'll start
04:31
up or at the very least going to be on a
04:33
background thread every time you use it
04:34
so what can we do here and the answer is
04:37
lazy lazy as a concept and dagger that
04:40
you've probably seen or even used but if
04:43
we look at our example here where can we
04:44
use this the trivial answer is we could
04:47
just apply it at the injection site and
04:50
this kind of works but you're just
04:51
kicking the can down the road a bit
04:52
because as soon as you use it then boom
04:54
you hit that cost again potentially on
04:57
the main thread or wherever you're
04:58
calling it from there's another way
05:00
there though so you see this client
05:04
retrofit speaks ok CDP and that's
05:07
nothing new what you might not realize
05:09
though is that this is actually just a
05:11
shorthand for another method called call
05:12
Factory now call Factory is an interface
05:15
in ok CDP that ok should defeat client
05:17
is just an implementer of and anything
05:20
else can implement this or you can also
05:21
just do plain simple delegation so more
05:25
simply because it's just a single method
05:26
interface we can put it in a lambda now
05:28
we're cooking with fire so the entire
05:30
client is actually just being wrapped up
05:33
and deferred in this and what that means
05:34
is that we can actually take this client
05:36
and make it lazy and this is actually
05:39
everything that we need for this example
05:41
now we deferred the entire
05:42
initialization of the client to the
05:44
first network called now here's the
05:46
kicker what thread is that called on
05:49
hopefully not the main thread so if
05:52
you're usually something like arc Java
05:53
or co-routines and doing your network
05:55
all off the main thread then that means
05:57
that this entire thing there we go ok so
06:04
this entire thing is now paid off of the
06:07
main thread and it's also amortized by
06:08
whatever the cost of your i/o is so if
06:13
you're doing an epic request that could
06:14
be 300 400 milliseconds if you're in
06:17
India that could be like 10 minutes the
06:20
100 milliseconds that you're paying on
06:22
startup you've now actually kicked to
06:23
whoever's waiting for this network
06:25
request and you know taking all those
06:29
and batching them into and every call
06:31
you're saving yourself a lot of time
06:32
here and in short you've taken all this
06:34
and
06:36
kicked it off to a background thread and
06:39
if you want to be super super sure that
06:40
you're actually getting the behavior
06:42
that you expect you could even stick
06:43
some main thread checks in there so
06:45
switching gears let's talk about modular
06:48
zation so module ization is a great way
06:50
to decoupled dependencies improve build
06:52
times with dagger you've I've got a
06:55
couple of tips for modularizing easily
06:57
first let's take a look at refactoring
06:59
so say I've got this giant module only
07:02
to here but imagine that there's like 50
07:04
and it's full of seasoning and taco
07:07
provisions so today I want to refactor
07:10
this a bit either I want it to be
07:11
smaller or I want to reuse some bits of
07:13
it a common pattern that I see people do
07:16
is do something like this where you're
07:18
pulling it out from the top and you
07:19
leave everything else in this sort of
07:20
base module that exists sort of in
07:23
perpetuity I usually think this is
07:25
actually the wrong thing to do though
07:26
because when you do this then you have
07:28
to update usages for everyone to point
07:30
to your new dependency now everything
07:32
you pull out a base is just going to
07:33
depend on base anyway so no one's really
07:34
getting any benefit of it but it's on
07:37
the right path though yeah the the right
07:39
spirit that we can make a couple tweaks
07:42
to this approach and I think that you'll
07:44
end up with something better so let's
07:46
look at this again but instead of making
07:47
a base module and pulling stuff out from
07:49
the top let's think of it a different
07:50
way and let's pull things out from the
07:52
bottom so in this case let's say pools
07:54
teasing out to its own module that my
07:56
giant module depends on this looks quite
07:59
similar but the key difference here is
08:01
that anyone depending on my giant module
08:03
didn't have to do anything your API for
08:06
consumers of that hasn't changed
08:08
you can continue iterating on this and
08:10
refactoring it under the hood it's all
08:11
just an implementation detail of how its
08:13
composed so this makes refactoring get
08:16
simple you can iterate on this without
08:19
churning people you can take more steps
08:21
along the way now at this point my giant
08:24
module is actually completely empty and
08:25
it's just a shin in front of the other
08:27
modules you can keep iterating and
08:29
tweaking things to to your heart's
08:30
content and then maybe at the end of
08:32
this you're finally able to just drop my
08:34
module or my giant module entirely this
08:37
part is a breaking change but you've
08:39
basically done all of the attorney
08:42
changes under the hood well before you
08:44
got there so this you can just do in one
08:46
atomic change and you're done
08:48
if you want you can even also give
08:50
consumers to this that you're working on
08:52
like a library team or platform team
08:54
where other people are using this and
08:56
you want to let them migrate at their
08:58
own cadence you can just deprecated it
09:00
and then you have all the replacement
09:03
pieces ready for them to use even better
09:07
though if you're using constructor
09:08
injection you don't need any of this in
09:11
fact if you're doing constructor
09:12
injection with these two then all you
09:14
need to do for your modules is that you
09:19
don't have to write any modules for this
09:21
because dagger can just construct them
09:22
for you this brings another interesting
09:24
point for module is a ssin though that's
09:26
worth mentioning what if these are
09:28
separate projects so say when we compile
09:32
feature a here if we have no prior code
09:34
gen done then dagger is gonna generate a
09:36
taco factory class this is what dagger
09:39
uses in Co gen to constructed and linked
09:42
dependencies into it now if we use this
09:44
didn't feature be the same thing is
09:46
going to happen now if I'm consuming
09:47
both of these features in my apps then
09:49
we're down the line what's going to
09:50
happen here is we're gonna actually have
09:51
a class path conflict between these two
09:56
because dagger compiled or generated
09:58
both of them you're trying to use them
09:59
both later and the compiler is going to
10:02
look at you and be like something's a
10:03
bit weird here so how can we solve this
10:05
and the solution here is pretty simple
10:07
you just run the dagger compiler over
10:09
the food sub project as well where this
10:10
lives and dagger will just generate the
10:12
factory there instead now in later
10:14
versions so in feature a and feature B
10:16
dagger will actually recognize that this
10:18
class was already generated and just
10:19
reused the same instance for everyone
10:22
let's cool on to the next tip let's talk
10:24
about internal and private api's
10:26
this one is pretty quick so let's look
10:28
back at that food module right now
10:30
anyone who wants to include this module
10:32
are going to get seasoning as a
10:33
dependency if they wanted to as well but
10:35
today I wanted to make seasoning a
10:37
private implementation detail of the
10:39
taco but I still want to use a separate
10:42
provider so we can use qualifiers in
10:45
dagger and compiler to do this for us
10:48
now at this time when I say compiler I
10:50
mean the actual Java compiler or Calvin
10:52
compiler so writing qualifier is easy
10:54
I'm just call it internal API now to use
10:58
this the trick here is to just make it
10:60
private
11:01
so I'm caught then you can just put this
11:03
in the same file as the top-level class
11:05
that you're using it in in Java you can
11:07
just put this as a inner class of the
11:10
module that you're using it in and then
11:12
you just take this annotation drop it in
11:14
on the dependency you want to make
11:17
private and now boom like seasoning is
11:20
no longer accessible outside of this
11:22
module because no one outside of this
11:25
class can actually even use that
11:26
annotation so they can't ask for it even
11:28
if they want to and so that's a sort of
11:31
roundabout way of letting the compiler
11:33
do work for you and guarding your your
11:36
dependencies the last area that I want
11:40
to cover is multi bindings so these are
11:42
all sensibly the coolest and most
11:43
powerful tools here and they're also
11:45
very open-ended into what you can do
11:46
with them they're pretty standard in D I
11:48
so of Jews dagger 1 dagger 2 all had
11:50
them and the core components to them are
11:53
maps and sets so a quick refresher set
11:57
is corresponding the annotations like
11:59
adding to said adding elements in the
12:00
set map is corresponding to things like
12:03
add into map and add map key you can
12:05
declare it upfront and basically just
12:07
collect multiple instances of a given
12:09
type that you can then use in bulk later
12:11
so let's look at our food module again
12:13
the same as before we have seasoning in
12:15
a taco but what if our taco accepts
12:16
multiple seasonings as most tacos should
12:18
we have a couple of types here so we've
12:21
got spicy seasoning although passive
12:22
seasoning but we've lost a little bit of
12:25
our di principle here though the talk of
12:27
provider at the bottom here knows quite
12:29
a bit about the seasonings coming into
12:31
it and this is where multi bindings will
12:34
really shine we can just accept a set
12:36
and provide into a set so we declare the
12:40
setup here and then we just add annotate
12:45
these providers with add in to set and
12:47
then at the bottom here we just accept a
12:50
set of seasonings and daggers canape so
12:51
all this information together this also
12:54
allows other modules to contribute into
12:56
a set so if I want to pull these
12:57
seasonings out to another module this
12:59
will still work fine and this is where
13:02
things become really powerful anywhere
13:04
where you're contributing multiple
13:05
instances of something can leverage this
13:07
kind of
13:08
tool so this is a trivial case but say
13:12
real world cases where you have
13:13
pluggable api's so ok GDP and it's
13:17
interceptors or maaske and json adapters
13:19
or timber and logging trees or more
13:22
advanced cases you could gate entire
13:25
features behind this in a DI friendly
13:26
way so let's make this a map and yeah
13:32
this in our screen here we've got
13:38
different categories for pets we've got
13:40
dogs and cats and birds I'm not totally
13:42
convinced that cats count as pets
13:43
because they're more like little tigers
13:44
in your house that say you want to wrap
13:50
them all up in this sort of category
13:51
based API now what if you want to make
13:53
them lazily initialized well in dagger
13:55
you can just wrap it up in lazy and
13:57
dagger we'll just natively understand
13:59
this and wrap them all and lazy for you
14:00
you can also do them with a provider to
14:03
guarantee new instances and no other
14:05
changes are required dagger is gonna do
14:07
all this for you under the hood it's
14:08
zero overhead to do this zero sourced
14:12
overhead so you could even implement an
14:16
entire app behind a plug-in system
14:17
backed by this so this is the I project
14:20
app of mine on github called ketchup and
14:22
each of those tabs is actually added in
14:24
a multi binding so this main pager
14:26
actually knows nothing about these
14:28
individual services it just gets a
14:30
mapped multi binding coming into it and
14:32
then unwraps those services and creates
14:34
the new instances for each of these tabs
14:37
in these maps you can just have keys at
14:40
it in here as metadata for the service
14:42
or like the ID some of these bar bits or
14:44
a trivial but what if you wanted to say
14:46
do experimentation flagging it's
14:48
actually really easy with this you add
14:50
this say experimentation ID and then
14:52
just build throughout your injection
14:54
site for whichever ones are enabled and
14:56
then you drop the ones that aren't ready
14:57
yet and that's it
15:02
these are some of the examples that
15:04
inspired a lot of this this blog post
15:08
here is a longer write-up of that
15:11
plug-in system that I was just
15:12
describing with my side project app
15:15
and yeah I hope you learned something
15:18
and if there's any questions I'll be
15:19
hanging out here on the side after
15:20
Thanks
15:23
[Applause]
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.