Untitled
Untitled
Untitled
Dagger by Tutorials
By Massimo Carli
Notice of Rights
All rights reserved. No part of this book or corresponding materials (such as text,
images, or source code) may be reproduced or distributed by any means without
prior written permission of the copyright owner.
Notice of Liability
This book and all corresponding materials (such as source code) are provided on an
“as is” basis, without warranty of any kind, express of implied, including but not
limited to the warranties of merchantability, fitness for a particular purpose, and
noninfringement. In no event shall the authors or copyright holders be liable for any
claim, damages or other liability, whether in action of contract, tort or otherwise,
arising from, out of or in connection with the software or the use of other dealing in
the software.
Trademarks
All trademarks and registered trademarks appearing in this book are the property of
their own respective owners.
raywenderlich.com 2
Dagger by Tutorials Dagger by Tutorials
Martyn Haigh is the other tech editor of this book. Martyn started
hacking on Android in 2008 and enjoyed it so much that he went
on to make a career out of it for names like Mozilla and Meet-up.
After over a decade of consulting, he’s currently working as a
senior developer at Facebook. He loves snowboarding, banging
coffee, amazing food and his gorgeous family.
raywenderlich.com 3
Dagger by Tutorials Dagger by Tutorials
raywenderlich.com 4
Dagger by Tutorials
raywenderlich.com 5
Dagger by Tutorials
raywenderlich.com 6
Dagger by Tutorials
Section I: DI Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . 22
Chapter 1: Design Principles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
What dependency means . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Types of dependencies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Abstraction reduces dependency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Using your superpower: abstraction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Finding the right level of abstraction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Composition over (implementation) inheritance . . . . . . . . . . . . . . . . . . . . . 38
Interface inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Why abstraction is important . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Challenge solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Key points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Where to go from here?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Chapter 2: Meet the Busso App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
The Busso App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Improving the Busso App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
The Rx Module for location . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
raywenderlich.com 7
Dagger by Tutorials
Key points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Chapter 3: Dependency Injection. . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Dependency injection. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Types of injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Busso App dependency management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Key points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Chapter 4: Dependency Injection & Scopes . . . . . . . . . . . . . . . . 102
Adding ServiceLocator to the Navigator implementation . . . . . . . . . . 103
Going back to injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
Key points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
Chapter 5: Dependency Injection & Testability . . . . . . . . . . . . 120
Model View Presenter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
View & ViewBinder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Presenter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Putting it all together . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Key points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Where to go from here? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
raywenderlich.com 8
Dagger by Tutorials
raywenderlich.com 9
Dagger by Tutorials
raywenderlich.com 10
Dagger by Tutorials
raywenderlich.com 11
Dagger by Tutorials
raywenderlich.com 12
L Book License
• You are allowed to use and/or modify the source code in Dagger by Tutorials in as
many apps as you want, with no attribution required.
• You are allowed to use and/or modify all art, images and designs that are included
in Dagger by Tutorials in as many apps as you want, but must include this
attribution line somewhere inside your app: “Artwork/images/designs: from
Dagger by Tutorials, available at www.raywenderlich.com.
• The source code included in Dagger by Tutorials is for your personal use only. You
are NOT allowed to distribute or sell the source code in Dagger by Tutorials
without prior authorization.
• This book is for your personal use only. You are NOT allowed to sell this book
without prior authorization, or distribute it to friends, coworkers or students; they
would need to purchase their own copies.
All materials provided with this book are provided on an “as is” basis, without
warranty of any kind, express or implied, including but not limited to the warranties
of merchantability, fitness for a particular purpose and noninfringement. In no event
shall the authors or copyright holders be liable for any claim, damages or other
liability, whether in an action of contract, tort or otherwise, arising from, out of or in
connection with the software or the use or other dealings in the software.
All trademarks and registered trademarks appearing in this guide are the properties
of their respective owners.
raywenderlich.com 13
Before You Begin
This section tells you a few things you need to know before you get started, such as
what you’ll need for hardware and software, where to find the project files for this
book, and more.
raywenderlich.com 14
i What You Need
raywenderlich.com 15
ii Book Source Code &
Forums
• https://github.com/raywenderlich/dag-materials/tree/editions/1.0
Forums
We’ve also set up an official forum for the book at
https://forums.raywenderlich.com/c/books/dagger-by-tutorials/. This is a great place
to ask questions about the book or to submit any errors you may find.
raywenderlich.com 16
iii About the Cover
Dagger by Tutorials
The cassowary is the large bird most closely related to the emu. It’s considered the
most dangerous bird in the world. The dagger-like claw on the inner toe of each foot
is what’s making cassowaries so dangerous. The cassowary can slice open any
predator or potential threat with a single swift kick.
Dagger by Tutorials illustrates the use of the Dagger library to manage dependencies
in the app. It’s not possible to create an application without dependencies. After
reading Dagger by Tutorials, you’ll be armed with Dagger and ready to make your
app testable and maintainable with a single dependency injection library.
raywenderlich.com 17
iv Introduction
You’re likely already doing dependency injection, maybe without even realizing it.
Dependency injection is nowhere near as complex as its name implies.
In this book, you’ll update an existing app named Busso to use dependency injection
with Dagger and Hilt. The Busso app is a simple app that allows you to find bus stops
near you and information about arrival times.
This book will serve you as a central point that holds all the information you need to
dive deep into Dagger and Hilt, to apply it to your personal and production level
projects.
raywenderlich.com 18
Dagger by Tutorials Introduction
If you’re familiar with the fundamentals of dependency injection, you can skip to
“Section II: Introducing Dagger” instead, and continue learning about dependency
injection with Dagger library.
If you’re already using Dagger in your projects, but want to know more about
complex topics, jump over to “Section IV: Advanced Dagger”. You’ll build complex
use cases there on a real-world project, learn about multi-binding and
modularization.
If you’re already proficient with Dagger library, you can skip to “Section V:
Introducing Hilt” and learn about dependency injection with Hilt on Android.
Section I: DI Fundamentals
In this section, you’ll get motivated to use a Dependency Injection (DI) library like
Dagger by learning all about the problem you need to solve: dependency.
You’ll understand what dependencies are and why you need to control them to
create successful apps. You’ll get to know the Busso App, which you’ll work on, and
improve throughout this book. It’s a client-server app where the server is
implemented using Ktor.
You’ll take your next step toward implementing better apps that are easier to test
and modify. You’ll keep the concept of mass of the project in mind. In the process,
you’ll learn more about Scope and see how it relates to dependency.
You’ll also use techniques that would work in a world without frameworks like
Dagger or Hilt to create a fully testable app.
You’ll learn how to deal with constructor, field and method injection with Dagger,
how to simplify the implementation of @Module by using @Binds in cases when
you have abstraction and its implementation, and how to use @Singleton to solve a
very common problem.
raywenderlich.com 19
Dagger by Tutorials Introduction
You’ll learn everything you need to know about Dagger @Modules and you’ll
experiment with using different fundamental annotations like @Binds, @Provides
and BindsOptionalOf as well as useful interfaces like dagger.Lazy and Provider.You’ll
also learn what qualifiers are, how to implement them with @Named and how to use
custom @Qualifiers.
The first migration will not be optimal — there will still be some fundamental
aspects you will improve.
Later, you’ll learn even more about @Components and dependencies. In particular,
you’ll learn why @Singleton is not so different from the other @Scopes, why you
might need a different approach in managing component dependencies, what type of
dependency exist between @Components with @Singleton, @ActivityScope and
@FragmentScope scope, etc.
You’ll implement a simple framework that allows you to integrate new services in
the Busso app in a very simple and declarative way. You’ll learn all you need to know
about multi-binding with Set and Map.
raywenderlich.com 20
Dagger by Tutorials Introduction
Hilt is built on top of the DI library Dagger to benefit from the compile-time
correctness, runtime performance, scalability, and Android Studio support that
Dagger provides.
raywenderlich.com 21
Section I: DI Fundamentals
In this section, understand why you should use a dependency injection (DI) library
like Dagger by learning all about the problem you need to solve: dependency.
You’ll understand what dependencies are and why you need to control them to
create successful apps. You’ll get to know the Busso App, which you’ll work on, and
improve throughout this book. And you’ll take your next step toward implementing
better apps that are easier to test and modify.
raywenderlich.com 22
1 Chapter 1: Design
Principles
By Massimo Carli
In this chapter, you’ll get motivated to use a Dependency Injection (DI) library like
Dagger by learning all about the problem you need to solve: dependency. You’ll
understand what dependencies are and why you need to control them to create
successful apps.
Note: This first chapter describes all the main concepts about object-oriented
programming using a dependency management focus. Go to the next chapter
if you’re already familiar with this topic.
When you thoroughly understand the object-oriented principles of this chapter, the
learning curve of those frameworks, initially steep, becomes flatter and everything
gets easier.
Note: You can find all the code for this chapter in the material section of this
book. It’s a good idea to follow along by adding the code to the starter project
with IntelliJ. You can also check out the complete code in the final project.
The challenge project contains the solutions to the challenges at the end of
the chapter.
raywenderlich.com 23
Dagger by Tutorials Chapter 1: Design Principles
But what does dependency mean? In the previous examples, it means that if the
person you depend on is not available anymore, you need to change your way of life.
If the dependency is economic, you have to reduce your expenses.
Similarly, if the business cuts the budget, you have to change the requirements for
your project or cancel it.
This concept is obvious in the last example because if x changes, y changes as well.
But why are changes important? Because it takes effort to make changes.
The previous examples showed how dependencies can cause changes, and you can
even prove this using physics. Consider the famous principle, Newton’s second law,
shown in Figure 1.1:
This doesn’t mean that change is bad. It just says that if you want to change, you
need to apply effort that’s as big or bigger than the mass m.
Hey, but this is a book about Dagger and Hilt! What does this have to do with your
code?
In the context of coding, dependency is inevitable, and applying changes will always
take some effort. But there are also ways to reduce the mass of your code, reducing
the effort, even for big changes.
This is what you’re going to learn in the following paragraphs. Mastering this skill
will allow you to use tools like Dagger and Hilt effectively and productively.
raywenderlich.com 24
Dagger by Tutorials Chapter 1: Design Principles
Note the “can” because this is something that could, but shouldn’t necessarily,
happen. In the previous examples, A can be you, your project or y. In the same
examples, B can be the person you depend on, the budget of your project or, simply,
x.
In these diagrams, A and B can be different things like objects, classes or even
packages or modules. This is a model that reflects what happens in software
development, where many components interact with many others in different ways.
raywenderlich.com 25
Dagger by Tutorials Chapter 1: Design Principles
On the other hand, it’s not possible to create an app without dependencies. If you
tried, you’d have the opposite problem of a monolithic app: All the code would be in
a single point, making writing code in large teams difficult and testing almost
impossible.
As a developer, one possible solution is to use patterns and practices that allow the
adoption of benign types of dependencies, which is the topic of the following
paragraphs.
Types of dependencies
Figure 1.3 above depicts a generic type of dependency, where the arrow simply
indicates that A depends on B without going into detail. It doesn’t show what A and
B are, or how the dependency looks in code.
Using object-oriented language, you can define relationships more precisely, and you
can do it in different ways for different types of dependencies. In the following
paragraphs you’ll learn about:
1. Implementation Inheritance
2. Composition
3. Aggregation
4. Interface Inheritance
You’ll also learn how abstraction can limit the impact of dependency.
raywenderlich.com 26
Dagger by Tutorials Chapter 1: Design Principles
Implementation inheritance
Implementation Inheritance is the strongest type of dependency. You describe it
using the UML diagram in Figure 1.4 below:
The Student class depends on the Person class because a change of the former has,
as an obvious consequence, a change in the latter — which is the very definition of
dependency.
For example, if you change the Person class by adding eat function, the Student
class now also has the ability to eat.
raywenderlich.com 27
Dagger by Tutorials Chapter 1: Design Principles
A Student differs from a generic Person because they study a particular subject. You
need both Person and Student classes due to two fundamental object-oriented
concepts: The first is that not all people study. The second, more important, concept
is that the fact that some people study may not interest you at all.
You have a Person class so you can generalize people of different types when the
only thing that interests you is that they are people.
For this reason, the statement Student IS-A Person is not the most correct way of
phrasing it. To be more accurate, you’d say: Person is a generalization or
abstraction of Student instead.
This app probably started with Student, then the developers added Person to limit
dependency. As you’ll see in the following paragraphs, they introduced a level of
abstraction to limit the dependency relationship.
Here, you can see that Person describes objects with a name and that they can
think(). A Student IS-A Person and so they can think() but they also study some
topics. All students are persons but not all persons are students.
raywenderlich.com 28
Dagger by Tutorials Chapter 1: Design Principles
fun think() {
println("$name is thinking...")
}
}
A Student has a name, can think and studies a topic. In Figure 1.5 below, you have
its UML representation.
fun main() {
val students = listOf<Student>(
Student("Mickey Mouse"),
Student("Donald Duck"),
Student("Minnie"),
Student("Amelia")
)
printStudent(students)
}
raywenderlich.com 29
Dagger by Tutorials Chapter 1: Design Principles
Mickey Mouse
Donald Duck
Minnie
Amelia
Your program works and everybody is happy… for now. But something is going to
change.
Handling change
Everything looks fine, but the university decided to hire some musicians and create a
band. They now need a program that prints the names of all the musicians in the
band.
You’re an expert now and you know how to model this new item, so you create the
Musician class like this:
Musicians have a name, they think and play a musical instrument. The UML diagram
is now this:
raywenderlich.com 30
Dagger by Tutorials Chapter 1: Design Principles
fun main() {
val musicians = listOf<Musician>(
Musician("Mozart"),
Musician("Andrew Lloyd Webber"),
Musician("Toscanini"),
Musician("Puccini"),
Musician("Verdi")
)
printMusician(musicians)
}
Mozart
Andrew Lloyd Webber
Toscanini
Puccini
Verdi
Everything looks fine and everybody is still happy. A good engineer should smell that
something is not ideal, though, because you copy-pasted most of the code. There’s a
lot of repetition, which violates the Don’t Repeat Yourself (DRY) principle.
Following the same approach, you ended up creating N different classes with N
different methods for printing N different lists of names.
Now, you’ve been asked to do a “simple” task. Instead of just printing the name, the
University asked you to add a Name: prefix. In code, instead of using:
println(it.name)
println("Name: $it.name")
Note: It’s curious how the customer has a different perception of what’s
simple and what isn’t.
raywenderlich.com 31
Dagger by Tutorials Chapter 1: Design Principles
Because of this request, you have to change N printing functions and the related
tests. You need to apply the same change in different places. You might miss some
and misspell others.
The probability of introducing bugs increases with the number of changes you need
to make.
If something bad happens, you need to spend a lot of time fixing the problem. And
even after that, you’re still not sure everything’s fine.
Note: There’s a joke about a consultant who wanted to make their employer
dependent on them. To do that, they only needed to implement the same
feature in many different ways and in many different places. This is no bueno!
To print names, you’re not interested in whether you have Student or Musician. You
don’t care if they study a topic or play an instrument. The only thing that interests
you is that they have a name.
You need a way to be able to see all the entries as if they were the same type,
containing the only thing that interests you: the name.
Here, the need to remove the superfluous leads you to the definition of the following
abstraction, which you call Person:
Abstraction means considering only the aspects that interest you by eliminating
everything superfluous. Abstraction is synonymous with reduction.
raywenderlich.com 32
Dagger by Tutorials Chapter 1: Design Principles
Knowing how to abstract, therefore, means knowing how to eliminate those aspects
that don’t interest you and, therefore, you don’t want to depend upon.
Creating Person means that you’re interested in the fact that a person can think and
you don’t care whether this person can study.
This is an abstract class. It allows you to define the Person type as an abstraction
only, thus preventing you from having to create an instance of it.
Now that you’ve put in the effort, you can reap the benefits of simplifying the
method of displaying names, which becomes:
The advantage lies in the fact that you can print the names of all the objects that can
be considered Person and, therefore, include both Student and Musician.
fun main() {
val persons = listOf(
Student("Topolino"),
Musician("Bach"),
Student("Minnie"),
Musician("Paganini")
)
printNames(persons)
}
raywenderlich.com 33
Dagger by Tutorials Chapter 1: Design Principles
And that’s not all. Returning to the concept of dependency, you can see that adding a
further specialization of Person does not imply any change in the printing function.
That’s because the only thing this depends on is the generalization described by
Person.
With this, you’ve shown how the definition of an abstraction can lead to a reduction
of dependency and, therefore, to changes having a smaller impact on the existing
code.
1. printNames() now depends on the Person abstraction. Even if you add a new
Person specialization, you won’t need to change printNames().
3. Student, Musician and Teacher are some of the realizations of the Person
abstract class. These are concrete classes that you can actually instantiate.
AnyOtherItem is an example of a concrete class you can add without impacting
printNames() in any way.
raywenderlich.com 34
Dagger by Tutorials Chapter 1: Design Principles
But what if you want to print the names for a list of objects for whom the IS-A
relation with Person is not true? What if you want to name cats, vehicles or food? A
cat is not a person, nor is food.
interface Named {
val name: String
}
Now, each person implements the Named interface. So do the Student, Musician,
Teacher and other realizations of the Person abstract class.
The good news is that now you can create Cat like this:
raywenderlich.com 35
Dagger by Tutorials Chapter 1: Design Principles
fun main() {
val persons = listOf(
Student("Topolino"),
Musician("Bach"),
Student("Minnie"),
Musician("Paganini"),
Cat("Silvestro")
)
printNames(persons)
}
Topolino
Bach
Minnie
Paganini
Silvestro
In the context of printing names, all the objects are exactly the same because they all
provide a name through a name property defined by the Named interface they
implement.
This is the first example of dependency on what a specific object DOES and not on
what the same component IS. You’ll learn about this in detail in the following
paragraphs.
raywenderlich.com 36
Dagger by Tutorials Chapter 1: Design Principles
2. Person implements the Named interface and you can now use each of its
realizations in printNames().
3. Cat implements the Named interface, printNames() can use it and it has nothing
to do with the Person class.
raywenderlich.com 37
Dagger by Tutorials Chapter 1: Design Principles
Now you can say that Cat as well as Student, Musician, Teacher and any other
realization of Person IS-A Named and printNames() can use them all.
This means that if you want to implement a new feature, you should add the new
thing without changing the existing code.
In the previous example, to add a new object compatible with printNames(), you
just need to make a new class that implements the Named interface. None of the
existing code needs to be changed.
This change is actually a big thing, because it’s your first example of dependency on
what an object DOES and not on what the same object IS.
In the printNames() example, this further reduced the dependency on the Person
abstraction.
This is a very important principle you should always consider in your app: Program
to an interface, not an implementation.
This principle is also true in real life. If you need a plumber, you don’t usually care
who that plumber is. What’s important is what they do. You want to hire the plumber
who can fix the pipes in your house.
Because of this, you can change who you use as your plumber if you have to. If you
need a specific plumber because of who they are, you need to consider a course of
action if they aren’t available anymore.
raywenderlich.com 38
Dagger by Tutorials Chapter 1: Design Principles
Composition
A classic example of dependency on what an object does is persistence
management.
Suppose you have a server that receives requests from clients, collects information
then stores it within a repository.
In this case, it would be completely wrong to say the Repository IS-A Server or the
Server IS-A Repository.
So if you were to represent the relationship between Server and Repository, you
could say that the former uses the latter, as the UML diagram in Figure 1.9 shows:
How do different entities communicate? In this case, Server must have a reference
to Repository and then invoke one or more methods on it. Here, you can suppose it
invokes save(Data) with a parameter of type Data.
You can represent the previous description with the following code:
class Repository {
fun save(data: Data) {
// Save data
}
}
class Server {
private val repository = Repository()
raywenderlich.com 39
Dagger by Tutorials Chapter 1: Design Principles
Everything looks perfect, but a problem arises as soon as you represent the previous
relationship through the UML diagram in Figure 1.10:
You can say that Server composes Repository. As you can see in the previous code,
Server has a private local variable of Repository. It initializes with an instance of
the Repository class itself.
• Repository and Server have the same lifecycle. The Repository instance is
created at the same time as the Server instance. Repository dies when Server
does.
raywenderlich.com 40
Dagger by Tutorials Chapter 1: Design Principles
Aggregation
For a better solution to this problem, you can use a different type of dependency:
aggregation. You represent it as in the UML diagram in Figure 1.11:
In this case, you pass the reference to a Repository instance in the constructor of
the Server class.
This means that Server is no longer responsible for creating the particular
Repository. This (apparently simple) change greatly improves your dependency
management.
You can now make Server use different Repository implementations simply by
passing a different instance in the constructor.
Now:
• Server doesn’t know exactly which implementation of the Repository it’s going
to use.
• Repository and Server may have different lifecycles. You can create the
Repository instance before the Server. Repository doesn’t necessarily die when
Server does.
Using a composition or aggregation doesn’t change the fact that Server depends
on Repository. The difference is in the type of dependency.
raywenderlich.com 41
Dagger by Tutorials Chapter 1: Design Principles
In the latter case, Repository can be an abstraction. This isn’t possible with
composition because you need to create an instance.
Note: You might ask why you need to support different Repository
implementations. As you’ll see in the following chapters, any abstraction
always has at least one additional implementation: the one you use in testing.
Interface inheritance
In the previous paragraphs, you learned the importance of abstractions and,
specifically, of using interfaces. In the Server and Repository example, you use an
aggregation relationship to make Repository an abstraction instead of using a
concrete class. This is possible because what the Server really needs is not an
instance of Repository but something that allows it to save some Data.
Note: The dependency is based on what the Repository DOES and not on
what the Repository IS.
For this reason, you can implement the following solution, where the Repository is
now an interface that might have different implementations, including the one you
can describe using the following RepositoryImpl class:
interface Repository {
fun save(data: Data)
}
raywenderlich.com 42
Dagger by Tutorials Chapter 1: Design Principles
You usually refer to this kind of dependency between Server and RepositoryImpl
as loosely coupled and describe it with the UML diagram in Figure 1.12:
raywenderlich.com 43
Dagger by Tutorials Chapter 1: Design Principles
You might ask, why do you need to manage different implementations for the
Repository abstraction? This is usually an interface to a database, the part of a
project that changes less frequently compared to the UI.As mentioned in a previous
note, you should always have at least one implementation of any abstraction for
testing. In Figure 1.12, this is called RepositoryMock.
How would you test the Server class you wrote for the composition case described in
Figure 1.10?
class Server {
private val repository = Repository()
If you want to test this class, you need to change it. If you change it, then this is not
the same class anymore. Consider, then, interface inheritance and the following
implementation:
raywenderlich.com 44
Dagger by Tutorials Chapter 1: Design Principles
Now, in your test, you just have to pass the mock implementation of the Repository
instance. If you use Mockito, this looks something like the following:
class ServerTest {
@Test
fun `When Data Received Save Method is Invoked On
Repository`() {
// 1
val repository = mock<Repository>()
// 2
val server = Server(repository)
// 3
val dataToBeSaved = Data(10)
server.receive(dataToBeSaved)
// 4
verify(repository).save(dataToBeSaved)
}
}
Here you:
4. Verify that you’ve invoked save() on the Repository mock with the expected
parameter.
Note: The previous code uses the Mockito testing framework, which is outside
the scope of this book. If you want to learn more about testing, read the
Android Test-Driven Development by Tutorials book. You can implement
the same test without Mockito, as you’ll see in the Challenge 3 for this
chapter.
This is a typical example of how thinking in terms of abstraction can help in the
creation, modification and testing of your code.
raywenderlich.com 45
Dagger by Tutorials Chapter 1: Design Principles
Challenges
Now that you’ve learned some important theory, it’s time for a few quick challenges.
raywenderlich.com 46
Dagger by Tutorials Chapter 1: Design Principles
Challenge solutions
Challenge solution 1: What type of
dependency?
In Figure 1.13, you have different types of dependency. Specifically:
Case 1
abstract class A
class B : A()
Case 2
open class A
class B : A()
Case 3
interface A
class B : A
raywenderlich.com 47
Dagger by Tutorials Chapter 1: Design Principles
Case 4
interface A
class B : A
class C(val a: A)
fun main() {
val a: A = B()
val c = C(a)
}
class ServerTest {
@Test
fun `When Data Received Save Method is Invoked On
Repository`() {
// 1
val repository = mock<Repository>()
// 2
val server = Server(repository)
// 3
val dataToBeSaved = Data(10)
server.receive(dataToBeSaved)
// 4
verify(repository).save(dataToBeSaved)
}
}
If the Mockito framework is not available, you need to define a mock implementation
for the Repository interface, then create RepositoryMock. A possible solution is:
// 1
class RepositoryMock : Repository {
// 2
var receivedData: Data? = null
raywenderlich.com 48
Dagger by Tutorials Chapter 1: Design Principles
Here you:
3. Implement save(), saving the received parameter data to the local variable
receivedData.
fun main() {
val repository = RepositoryMock()
val server = Server(repository)
val dataToBeSaved = Data(10)
server.receive(dataToBeSaved)
assert(repository.receivedData == dataToBeSaved) // HERE
}
The test is successful if the value of the receivedData equals the value passed to the
receive() function of the server. Of course, using a framework like JUnit or Mockito
makes the testing experience better from the engineering tools perspective, but the
point doesn’t change.
Note: In this case, you’re not actually testing interaction but state, so the
proper name for RepositoryMock should be RepositoryFake.
raywenderlich.com 49
Dagger by Tutorials Chapter 1: Design Principles
Key points
• Dependency is everywhere — you can’t avoid it altogether.
• In computer science, you need to control dependency using the proper patterns.
• It’s better to depend on what an entity DOES rather than what an entity IS.
If you want to learn more about the concepts and libraries of this chapter, take a look
at:
raywenderlich.com 50
2 Chapter 2: Meet the Busso
App
By Massimo Carli
In the first chapter of this book, you learned what dependency means, what the
different types of dependencies are and how they’re represented in code. You learned
details about:
• Implementation Inheritance
• Composition
• Aggregation
• Interface Inheritance
You saw examples of each type of dependency and you understood which works
better in various situations. Using UML diagrams, you also learned why dependency
is something you need to control if you want your code to be maintainable. You saw
why applying these principles using design patterns is important to make your code
testable.
So far, this book has contained a lot of theory with many concepts you need to
master if you want to successfully use libraries like Dagger or Hilt for Dependency
Injection (DI) on Android. Now, it’s time to move beyond theory and start coding.
In this chapter, you’ll get to know the Busso App, which you’ll work on and improve
throughout this book. It’s a client-server app where the server is implemented using
Ktor.
raywenderlich.com 51
Dagger by Tutorials Chapter 2: Meet the Busso App
You’ll start by installing the server locally, or just using the pre-installed version on
Heroku. Then you’ll configure, build and run the Busso Android App.
The version of the app you start with is basic, not something to be proud of. You’ll
spend the last part of the chapter understanding why and taking the first steps to
improve it.
1. The big boxes represent physical machines like computers, devices or servers.
You call them nodes.
2. The boxes with the two small rectangles on the left edge are components. The
Busso App component lives in the device while the Busso Server lives on a
server machine, probably in the cloud.
3. The Busso Server exposes an interface you represent using something called a
lollipop. You can use a label to give information about the specific protocol used
in the communication — in this case, HTTP.
4. The Busso App interacts with the HTTP interface the Busso Server provides.
Represent this with a semicircle embracing the lollipop.
Before going into the details of these components, run the app using the following
steps.
raywenderlich.com 52
Dagger by Tutorials Chapter 2: Meet the Busso App
Note: You don’t need to know the details now, but if you’re curious, you can
read more about Busso Server in the Appendix of this book.
Note: If you don’t want to run the Busso Server locally, there’s an existing
installation running on Heroku. Just skip to the next section to find out how
to use it.
Open the BussoServer project and you’ll get the following structure of directories:
raywenderlich.com 53
Dagger by Tutorials Chapter 2: Meet the Busso App
Now, your next step is to connect the server to the Busso Android App.
raywenderlich.com 54
Dagger by Tutorials Chapter 2: Meet the Busso App
Note: If you use the Busso Server on Heroku, you can skip this configuration
and follow the instructions in the section, “Running the Busso Server on
Heroku”.
raywenderlich.com 55
Dagger by Tutorials Chapter 2: Meet the Busso App
The Busso App doesn’t know where to connect yet. You need to change this to
include the IP of your server. But how do you determine what that IP is? You need to
find the IP of your server machine in your local network.
Note: You can’t just use localhost or 127.0.0.1 because that would be the IP
address of your Android device, not the device where the Busso Server is
running.
If you’re using a Mac, open a terminal and run the following command if you’re using
ethernet:
# 192.168.1.124
Remember that your specific IP will be different from the one shown above.
On Windows, run the ifconfig command to get the same information from a
terminal prompt.
Now, in Configuration.kt, replace <YOUR SERVER IP> with your IP. With the
previous value, your code would be:
raywenderlich.com 56
Dagger by Tutorials Chapter 2: Meet the Busso App
Figure 2.6 — Allow the HTTP protocol from the Android Client
You’ll get the following XML content:
Next, replace <!-- YOUR SERVER IP --> with the same IP you got earlier.
Using the IP value from the previous example, you’d end up with:
raywenderlich.com 57
Dagger by Tutorials Chapter 2: Meet the Busso App
raywenderlich.com 58
Dagger by Tutorials Chapter 2: Meet the Busso App
Of course, if you want to use the app, you have to select the Allow while using the
app option. This will bring you to the screen shown in Figure 2.9:
You’re getting fake data — you’re not necessarily in London :] — but that data comes
from the Busso Server. For each bus stop, you’ll see something similar to Figure 2.10:
raywenderlich.com 59
Dagger by Tutorials Chapter 2: Meet the Busso App
• The name of the bus stop. For example, Piccadilly Circus Haymarket.
Now, select one of the cards and you’ll come to a second screen:
The Busso App is now running and you’re ready to start the journey through design
principles and, specifically, dependency injection.
raywenderlich.com 60
Dagger by Tutorials Chapter 2: Meet the Busso App
https://busso-server.herokuapp.com/
• You don’t overload your machine running the Busso Server Process.
• The app can use the HTTPS protocol, while the local installation uses HTTP.
Using the HTTPS protocol, you don’t need the configuration in Figure 2.6
anymore.
You can easily verify that the server is up and running by accessing the previous URL
with your favorite browser. If you use Chrome, you’ll get what is shown in Figure
2.12:
Next, you need to put a valid value into the xml resource folder in
network_security_config.xml, like this:
raywenderlich.com 61
Dagger by Tutorials Chapter 2: Meet the Busso App
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">0.0.0.0</domain>
</domain-config>
</network-security-config>
The specific IP you use here isn’t important as long as it’s a valid IP address.
• A lot of copied and pasted code that leads to repetition you should avoid.
• No unit tests.
In the following sections, you’ll learn more about these problems and get some ideas
for solving them.
Reducing repetition
SplashActivity.kt contains the following code:
raywenderlich.com 62
Dagger by Tutorials Chapter 2: Meet the Busso App
Here, you:
The code in onAttach() does basically the same thing as the previous example,
because it:
raywenderlich.com 63
Dagger by Tutorials Chapter 2: Meet the Busso App
raywenderlich.com 64
Dagger by Tutorials Chapter 2: Meet the Busso App
or)
)
}
}
As you can see, NavigatorImpl depends on the Activity that it accepts as the
parameter in its primary constructor.
This means that NavigatorImpl should have the same lifecycle as the Activity you
use for its creation. This currently isn’t happening, as you can see in onAttach() in
BusStopFragment.kt:
raywenderlich.com 65
Dagger by Tutorials Chapter 2: Meet the Busso App
super.onAttach(context)
locationManager =
context.getSystemService(Context.LOCATION_SERVICE) as
LocationManager
locationObservable =
provideRxLocationObservable(locationManager,
grantedPermissionChecker)
navigator = NavigatorImpl(context as Activity) // HERE
}
What isn’t obvious in the diagram is that each component living within a scope
should have access to the instance living in an external scope.
raywenderlich.com 66
Dagger by Tutorials Chapter 2: Meet the Busso App
For instance, any component should be able to access the same BussoEndpoint
implementation, Fragments living in a specific Activity should share the same
instance of the Navigator implementation, and so on.
Don’t worry if this isn’t clear yet. You’ll learn a lot about this concept in the
following chapters.
Note: As you’ll see later in this chapter, the Rx Module for Location contains
some tests. They’re in a different module, though.
As it is now, the Busso App is almost impossible to test. Just have a look at
BusStopFragment.kt. How would you test a function like this?
In the following chapters, you’ll see how using dependency injection and other
design patterns will make the Busso App easy to test.
raywenderlich.com 67
Dagger by Tutorials Chapter 2: Meet the Busso App
raywenderlich.com 68
Dagger by Tutorials Chapter 2: Meet the Busso App
Every API implementation should provide some events of the LocationEvent type
in LocationEvent.kt. This is a sealed class that defines the following specific
subtypes:
• LocationPermissionRequest
• LocationPermissionGranted
• LocationNotAvailable
• LocationData
• LocationStatus
• LocationProviderEnabledChanged
The events’ names are self-explanatory but it’s important to note that
LocationPermissionRequest is an event that fires when the permission to access
the user’s location is missing, and that you need to put some request permission
procedure in place.
The most important event is LocationData, which contains the information about
the location in an object of type GeoLocation.
Permission can be granted in many ways, so you need an abstraction like the one
defined by:
interface GeoLocationPermissionChecker {
val isPermissionGiven: Boolean
}
The Rx module contains an implementation of the previous APIs that use RxJava or
RxKotlin. You can take a look at its logic in RxLocationObservable.kt.
Note: RxJava is a library that implements the React specification. It’s used in
many commercial apps. This book is not about RxJava, but you can learn all
about it in the Reactive Programming With Kotlin book.
raywenderlich.com 69
Dagger by Tutorials Chapter 2: Meet the Busso App
@Test
fun
whenPermissionIsDeniedLocationPermissionRequestIsSentAndThenComp
letes() {
rxLocationTest(context) {
Given {
permissionIsDenied()
}
When {
subscribeRx()
}
Then {
permissionRequestIsFired()
isComplete()
}
}
}
Note: The Robot Pattern is a useful testing pattern that allows you to write
more readable tests. You can learn all about the Robot pattern and other
testing procedures in the Android Test-Driven Development by Tutorials
(https://www.raywenderlich.com/books/android-test-driven-development-by-
tutorials) book.
raywenderlich.com 70
Dagger by Tutorials Chapter 2: Meet the Busso App
Challenge
Challenge 1: Some unit tests as a warm-up
After building and running the Busso App, it’s time for a nice challenge.As you know,
the Busso App doesn’t have unit tests. Can you write some for the code related to the
BusStopMapper.kt and BusArrivalMapper.kt files, as shown in Figure 2.17?
raywenderlich.com 71
Dagger by Tutorials Chapter 2: Meet the Busso App
BusStop contains pure data about a bus stop, which you get from the server. It looks
like this:
Tests allow you to write better code. In this case, mapBusStop() is pure, so you have
to verify that for a given input, the output is what you expect.
raywenderlich.com 72
Dagger by Tutorials Chapter 2: Meet the Busso App
Open BusStopMapper.kt and select the name of mapBusStop(). Now, open the
quick actions menu with Control - Enter to what’s shown in Figure 2.18:
raywenderlich.com 73
Dagger by Tutorials Chapter 2: Meet the Busso App
Now, Android Studio will create a new file for you, like this:
class BusStopMapperKtTest {
@Test
fun mapBusStop() {
}
@Test
fun testMapBusStop() {
}
}
The first question you need to ask yourself when writing a unit test is: What am I
testing?
In this case, the answer is that, given a BusStop, you need to get the expected
BusStopViewModel. This must be true in the happy case and in all the edge cases.
@Test
fun
mapBusStop_givenCompleteBusStop_returnsCompleteBusStopViewModel(
) {
// 1
val inputBusStop = BusStop(
"id",
"stopName",
GeoLocation(1.0, 2.0),
"direction",
"indicator",
123F
)
// 2
val expectedViewModel = BusStopViewModel(
"id",
"stopName",
"direction",
"indicator",
"123 m"
)
// 3
assertEquals(expectedViewModel, mapBusStop(inputBusStop))
}
raywenderlich.com 74
Dagger by Tutorials Chapter 2: Meet the Busso App
Now, you can run the test selecting the arrow as in Figure 2.21:
As an exercise, add the missing tests and check if they’re similar to the ones you’ll
find in the final project for this chapter.
raywenderlich.com 75
Dagger by Tutorials Chapter 2: Meet the Busso App
Key points
• The Busso App is a client-server app.
• The Busso Server has been implemented with Ktor. You can run it locally or use
the existing Heroku installation.
• The Busso App works, but you can improve it by removing code duplication and
adding unit tests.
• The concept of scope or lifecycle is fundamental and you’ll learn much more
about it throughout this book.
raywenderlich.com 76
3 Chapter 3: Dependency
Injection
By Massimo Carli
In the first chapter, you learned what dependency means and how you can limit its
impact during the development of your app. You learned to prefer aggregation over
composition because that allows you to change the implementation of Repository
without changing the implementation of Server, as described by the following UML
diagram:
With this pattern, Server has no responsibility for the creation of the specific
Repository. That syntax is just saying that Server needs a Repository to work. In
other words, Server depends on Repository.
In the second chapter, you looked at the Busso App. You learned how to build and
run both the server and the Android app. You also looked at its code to understand
how the RxLocation and Navigator modules work. More importantly, you learned
why the architecture for the Busso App is not the best and what you could do to
improve its quality.
raywenderlich.com 77
Dagger by Tutorials Chapter 3: Dependency Injection
In this chapter, you’ll take your next step toward implementing a better app that’s
easier to test and modify. You’ll keep the concept of mass of the project in mind,
which you saw in the first chapter.
You’ll start by refactoring the Busso App in a world without Dagger or Hilt. This is
important if you want to really understand how those frameworks work and how you
can use them to solve the dependency problem in a different, easier way.
Dependency injection
Looking at the previous code, which component is responsible for the creation of the
Repository implementation you need to pass as parameter of the Server primary
constructor?
It’s Main, which contains all the “dirty” code you need to create the necessary
instances for the app, binding them according to their dependencies.
OK, so how do you describe a dependency between different objects? You just follow
some coding rules, like the one you already used in the Server/Repository example.
By making Repository a primary constructor parameter for Server, you explicitly
defined a dependency between them.
fun main() {
// 1
val repository = RepositoryImpl()
// 2
val server = Server(repository)
// ...
val data = Data()
server.receive(data)
// ...
}
You can say that the Main component injects a Repository into Server.
raywenderlich.com 78
Dagger by Tutorials Chapter 3: Dependency Injection
By changing Main, you modify what you can inject. This reduces the impact of a
change, thus reducing dependency.
Note: Spoiler alert! Looking at the previous code, you understand that Server
needs a Repository because it’s a required parameter of its primary
constructor. The Server depends on the Repository. Is this enough to
somehow generate the code you have into main()? Sometimes yes, and
sometimes you’ll need more information, as you’ll see in the following
chapters.
Currently, the Busso App doesn’t use this method, which makes testing and changes
in general very expensive.
In the following sections of this chapter, you’ll start applying these principles to the
Busso App, improving its quality and reducing its mass.
Types of injection
In the previous example, you learned how to define a dependency between two
classes by making Repository a required constructor parameter for Server. This is
just one way to implement dependency injection. The different types of injection
are:
• Constructor injection
• Field injection
• Method injection
Take a closer look at each of these now so you can use them in the Busso App later.
raywenderlich.com 79
Dagger by Tutorials Chapter 3: Dependency Injection
Constructor injection
This is the type of injection you saw in the previous example, where the dependent
type (Server) declares the dependency on a dependency type (Repository) using
the primary constructor.
In the code above, you can’t create a Server without passing the reference of a
Repository. The former depends on the latter.
Also, note the presence of the private visibility modifier, which makes the
repository property read-only and Server class immutable. This is possible
because the binding between the two objects happens during the creation of the
dependent one — Server, in this case.
For the same reason, this is the best type of injection you can achieve if you
have control over the creation of the components in the dependency relation.
Field injection
Constructor injection is the ideal type of injection but, unfortunately, it’s not
always possible. Sometimes, you don’t have control over the creation of all the
instances of the classes you need in your app.
Note: The same is true for the other Android standard components
represented by classes like Service, ContentProvider and
BroadcastReceiver. If you think about it, these are the things you describe to
the Android container using the AndroidManifest.xml file.
raywenderlich.com 80
Dagger by Tutorials Chapter 3: Dependency Injection
A possible alternative is to define a property whose value is set after the creation
of the instance it belongs to. The type of the property is the dependency. This is
called a property injection, which you can implement with the following code:
class Server () {
lateinit var repository: Repository // HERE
Using lateinit var ensures you’ve initialized the corresponding property before
you use it. In this case, Main must obtain the reference to the Repository and then
assign it to the related property, as in the following code:
fun main() {
// 1
val repository = RepositoryImpl()
// 2
val server = Server()
// 3
server.repository = repository
// ...
val data = Data()
server.receive(data)
// ...
}
Here you:
2. Create the instance for Server, whose primary constructor is the default one —
the one with no parameters.
A possible hiccup is that Server’s state is inconsistent between points 2 and 3. This
might cause problems in concurrent systems.
raywenderlich.com 81
Dagger by Tutorials Chapter 3: Dependency Injection
Note: This book uses Kotlin, which doesn’t have the concept of an instance
variable of a class; it allows you to define properties instead. A property is
the characteristic of an object that can be seen from the outside. This happens
by using particular methods called accessor and mutator. The former are
usually (but not necessarily) methods with the prefix get, while the latter
methods start with set.
For this reason, the definition of field injection in Kotlin can be a bit
confusing. Don’t worry, everything will be clear when you learn how to
implement this with Dagger.
As mentioned, field injection is very important. It’s the type of injection you’ll often
find when, while developing Android apps, you need to inject objects into Fragment
or other standard components, like the ones mentioned earlier.
Method injection
For completeness, take a brief look at what method injection is. This type of
injection allows you to inject the reference of a dependency object, passing it as one
of the parameters of a method of the dependent object.
class Server() {
private var repository: Repository? = null
Using method injection, you assume that null is valid as an initial value for the
repository property. In this case, you declare that Server can use a Repository,
but it doesn’t need to. This is why you don’t use a lateinit var, like you would
with a field injection, and you use the ?. (safe call operator) while accessing the
repository property into the receive() function.
raywenderlich.com 82
Dagger by Tutorials Chapter 3: Dependency Injection
In this example, Main can invoke fixRepo() to set the dependency between Server
and Repository, as in the following code:
fun main() {
val repository = RepositoryImpl()
val server = Server()
server.fixRepo(repository) // HERE
// ...
val data = Data()
server.receive(data)
// ...
}
Unlike field injection, method injection gives you the ability to inject multiple values
with the same method, in case the method has more than one parameter. For
instance, you might have something like:
class Dependent() {
private var dep1: Dep1? = null
private var dep2: Dep2? = null
private var dep3: Dep3? = null
In this case, the problem is that you need to pass all the dependencies, even when
you only need to set some of them.
Use Android Studio and open the starter project that’s in the material for this
chapter.
Note: The starter project uses the existing Heroku server, but you can
configure it for using a local server using the instructions in Chapter 2, “Meet
the Busso App”.
raywenderlich.com 83
Dagger by Tutorials Chapter 3: Dependency Injection
Build and run the Busso App, checking everything works as expected and you get
what’s shown in Figure 3.2:
Dependency graph
When you want to improve the quality of any app, a good place to start is by defining
the dependency graph.
In the examples above, you only had two objects: Server and Repository. In a real
app, you often have more classes that depend on each other in many different ways.
raywenderlich.com 84
Dagger by Tutorials Chapter 3: Dependency Injection
In the previous chapter, you learned how to represent dependencies using a UML
diagram. With the same language, you can create the dependency diagram in
Figure 3.3:
raywenderlich.com 85
Dagger by Tutorials Chapter 3: Dependency Injection
This dependency diagram is the map you need to refer to when you want to manage
dependencies in your app. It’s a representation of a dependency graph, which is the
set of all the objects an app uses, connected according to their dependencies.
In the next section, you’ll learn how to use this diagram in the Busso App.
A good place to start is with the definition of the Main object. This is the object
responsible for the creation of the dependency graph for the app.
raywenderlich.com 86
Dagger by Tutorials Chapter 3: Dependency Injection
Start with a component responsible for providing the reference to the objects you
need. This is the idea behind the service locator design pattern.
interface ServiceLocator {
/**
* Returns the object of type A bound to a specific name
*/
fun <A : Any> lookUp(name: String): A
}
In the first chapter, you learned to always think of interface. That’s what you’ve
done with the ServiceLocator interface, which is the abstraction for the homonym
design pattern. This interface defines the lookUp() operation, which, given a
specific object’s name, returns its reference.
raywenderlich.com 87
Dagger by Tutorials Chapter 3: Dependency Injection
After this, the project should have a structure like in Figure 3.4:
Note: When you assign the value you get from a ServiceLocator using its
lookUp() operation to a lateinit var, you’re not actually using injection.
Rather, you’re using dependency lookup. You usually do this on the server
side with abstractions like Java Naming and Directory Interface (JNDI).
Now you can start using the ServiceLocator in the Busso App.
raywenderlich.com 88
Dagger by Tutorials Chapter 3: Dependency Injection
}
}
// 3
internal fun <A: Any> AppCompatActivity.lookUp(name: String): A
=
(applicationContext as Main).serviceLocator.lookUp(name)
Exercise 3.1: If you want to use TDD, you should already start writing the unit
test for ServiceLocatorImpl.
Main is a custom Application for the Busso App that you need to declare to the
Android environment by adding the following definition to AndroidManifest.xml,
which is in the manifests folder in Figure 3.5:
raywenderlich.com 89
Dagger by Tutorials Chapter 3: Dependency Injection
<application
android:name=".Main" <!-- The Main component-->
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<!-- ... -->
</application>
</manifest>
3. Use the same name to look up the reference to the registered objects from any of
the Busso App activities.
Look at the diagram in Figure 3.2. This shows you can start with LocationManager
which you don’t use directly from the SplashActivity. Instead,
Observable<LocationEvent> depends on LocationManager.
Then, open ServiceLocatorImpl.kt and replace the current code with the following:
// 1
const val LOCATION_MANAGER = "LocationManager"
class ServiceLocatorImpl(
// 2
val context: Context
) : ServiceLocator {
// 3
@Suppress("UNCHECKED_CAST")
@SuppressLint("ServiceCast")
override fun <A : Any> lookUp(name: String): A = when (name) {
// 4
raywenderlich.com 90
Dagger by Tutorials Chapter 3: Dependency Injection
LOCATION_MANAGER ->
context.getSystemService(Context.LOCATION_SERVICE)
else -> throw IllegalArgumentException("No component lookup
for the key: $name")
} as A
}
3. You need to challenge the Kotlin inference mechanism here a little bit, forcing
the cast to the generic type A by adding @Suppress("UNCHECKED_CAST") and
@SuppressLint("ServiceCast") annotations.
4. You just need to add the entry for the LOCATION_MANAGER, returning what you get
from the Context through getSystemService().
Note: Oh, look! Android already uses the ServiceLocator pattern with
Context and getSystemService().
Now, you need a small change in Main.kt, too. Now that the ServiceLocatorImpl
primary constructor needs the Context, you need to change it like this:
This is possible because the Application IS-A Context. Now you have an object
responsible for the creation of the instances of the classes the Busso App needs.
At the moment, this is only true for the LocationManager. For your next step, you’ll
start using it in the SplashActivity.
raywenderlich.com 91
Dagger by Tutorials Chapter 3: Dependency Injection
This lets you get LocationManager using lookUp() with the proper parameter.
Note: It’s important that the type for the local variable locationManager
must be explicit to help Kotlin in the type inference of the value you get from
the lookup.
raywenderlich.com 92
Dagger by Tutorials Chapter 3: Dependency Injection
raywenderlich.com 93
Dagger by Tutorials Chapter 3: Dependency Injection
/**
* Implementation for the ServiceLocator
*/
class ServiceLocatorImpl(
val context: Context
) : ServiceLocator {
@Suppress("UNCHECKED_CAST")
raywenderlich.com 94
Dagger by Tutorials Chapter 3: Dependency Injection
@SuppressLint("ServiceCast")
override fun <A : Any> lookUp(name: String): A = when (name) {
LOCATION_MANAGER ->
context.getSystemService(Context.LOCATION_SERVICE)
// 2
GEO_PERMISSION_CHECKER ->
GeoLocationPermissionCheckerImpl(context)
else -> throw IllegalArgumentException("No component lookup
for the key: $name")
} as A
}
// TO BE REMOVED
private val permissionChecker = object :
GeoLocationPermissionChecker {
override val isPermissionGiven: Boolean
get() = ContextCompat.checkSelfPermission(
this@SplashActivity,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
raywenderlich.com 95
Dagger by Tutorials Chapter 3: Dependency Injection
Build and run and you’ll get the result shown in Figure 3.8:
raywenderlich.com 96
Dagger by Tutorials Chapter 3: Dependency Injection
Refactoring Observable<LocationEvent>
As mentioned above, the dependency diagram is useful when you need to improve
the quality of your code. Look at the detail in Figure 3.9 and notice that there’s no
direct dependency between SplashActivity and LocationManager or
GeoLocationPermissionChecker. SplashActivity shouldn’t even know these
objects exist.
raywenderlich.com 97
Dagger by Tutorials Chapter 3: Dependency Injection
You can easily fix this problem by changing the code in ServiceLocatorImpl.kt to
the following:
// 1
const val LOCATION_OBSERVABLE = "LocationObservable"
class ServiceLocatorImpl(
val context: Context
) : ServiceLocator {
// 2
private val locationManager =
context.getSystemService(Context.LOCATION_SERVICE) as
LocationManager
// 3
private val geoLocationPermissionChecker =
GeoLocationPermissionCheckerImpl(context)
// 4
private val locationObservable =
provideRxLocationObservable(locationManager,
geoLocationPermissionChecker)
@Suppress("UNCHECKED_CAST")
@SuppressLint("ServiceCast")
override fun <A : Any> lookUp(name: String): A = when (name) {
// 5
LOCATION_OBSERVABLE -> locationObservable
else -> throw IllegalArgumentException("No component lookup
for the key: $name")
} as A
}
In this code, there are some important things to note. Here, you:
1. Define LOCATION_OBSERVABLE, which is now the only dependency you’ll need for
the lookup.
5. Delete the existing cases and add the one related to LOCATION_OBSERVABLE.
Due to point 4, when you invoke lookUp(), you always return the reference to the
same object.
raywenderlich.com 98
Dagger by Tutorials Chapter 3: Dependency Injection
Now, you just need to add this to SplashActivity.kt, changing onCreate() like this:
Finally, build and run again and check again that everything works as expected.
Challenge
Challenge 1: Testing ServiceLocatorImpl
By following the same process you saw in the previous chapter, create a test for
ServiceLocatorImpl. At this moment, you can implement the test as:
@RunWith(RobolectricTestRunner::class)
class ServiceLocatorImplTest {
// 1
@Rule
@JvmField
var thrown: ExpectedException = ExpectedException.none()
// 2
lateinit var serviceLocator: ServiceLocatorImpl
@Before
fun setUp() {
// 3
serviceLocator =
ServiceLocatorImpl(ApplicationProvider.getApplicationContext())
}
@Test
fun lookUp_whenObjectIsMissing_throwsException() {
// 4
thrown.expect(IllegalArgumentException::class.java)
// 5
serviceLocator.lookUp<Any>("MISSING")
}
}
raywenderlich.com 99
Dagger by Tutorials Chapter 3: Dependency Injection
4. Then, you implement the function for the test annotated with @Test, starting
with the definition of the expected exception.
Now, run the tests and, if successful, you’ll get a green bar!
raywenderlich.com 100
Dagger by Tutorials Chapter 3: Dependency Injection
Key points
• Dependency Injection describes the process in which an external entity is
responsible for creating all the instances of the components an app requires,
injecting them according to the dependency rules you define.
• Main is the component responsible for the creation of the dependency graph for
an app.
• The main type of injections are constructor injection, field injection and
method injection.
• Constructor injection is the preferable injection type, but you need control over
the lifecycle of the object’s injection destination.
• Service Locator is a pattern you can use to access the objects of the dependency
graph, given a name.
In this chapter, you learned what dependency injection means and what the
different types of injections you can use in your code are. You also started to refactor
the Busso App in a world where frameworks like Dagger and Hilt don’t exist.
In that world, you defined a simple implementation for the ServiceLocator pattern
and you started using it in the Busso App for LocationManager,
GeoLocationPermissionChecker and, finally, Observable<LocationEvent>.
Is this process still valid for components like Navigator? Are the lifecycles of all the
objects the same? In the next chapter, you’ll find that there are still things to
improve in the app.
raywenderlich.com 101
4 Chapter 4: Dependency
Injection & Scopes
By Massimo Carli
In the previous chapter, you learned what dependency injection is and how to use it
to improve the architecture of the Busso App. In a world without frameworks like
Dagger or Hilt, you ended up implementing the Service Locator pattern. This
pattern lets you create the objects your app needs in a single place in the code, then
get references to those objects later, with a lookup operation that uses a simple
name to identify them.
You then learned what dependency lookup is. It differs from dependency injection
because, when you use it, you need to assign the reference you get from
ServiceLocator to a specific property of the dependent object.
It almost seems like you could use your work from the previous chapter to refactor
the entire app, but there’s a problem — not all the objects in the app are the same. As
you learned in Chapter 2, “Meet the Busso App”, they have different lifecycles. Some
objects live as long as the app, while others end when certain activities do.
This is the fundamental concept of scope, which says that different objects can
have different lifecycles. You’ll see this many times throughout this book.
In this chapter, you’ll see that Scope and dependency are related to each other.
You’ll start by refactoring how SplashActivity uses Navigator. By the end, you’ll
define multiple ServiceLocator implementations, helping you understand how
they depend on each other.
raywenderlich.com 102
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
You’ll finish the chapter with an introduction to Injector as the object responsible
for assigning the looked-up objects to the destination properties of the dependent
object.
Now that you know where you’re heading, it’s time to get started!
raywenderlich.com 103
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
Figure 4.2 — Class Diagram for the Main and SplashActivity classes
In this class diagram, note that:
1. Activity extends the Context abstract class. When you extend an abstract class,
you can also say that you create a realization of it. Activity is, therefore, a
realization of Context.
raywenderlich.com 104
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
Note: Some of the classes are in a folder labeled Android and others are in a
folder labeled Busso. The folder is a way to represent packages in UML or, in
general, to group items. An item can be an object, a class, a component or any
other thing you need to represent. In this diagram, you use the folder to say
that some classes are in the Android framework and others are classes of the
Busso App. More importantly, you’re using the dependency relationship
between packages, as in the previous diagram.
The class diagram also explicitly says that Main IS-NOT-A Activity.
You can see the same in NavigatorImpl.kt inside the libs/ui/navigation module:
From Main, you don’t have access to the Activity. The Main class IS-A Application
that IS-A Context, but it’s not an Activity. The lifecycle of an Application is
different from the Activity’s.
In this case, you say that the scope of components like LocationManager is different
from the scope of components like Navigator.
But how can you manage the injection of objects with different scopes?
raywenderlich.com 105
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
This is a simple typealias. Type aliases are a way to provide a shorter or more
meaningful name for an existing type. This typealias provides a shorter name to the
type of a factory function from an object of type A to an implementation of
ServiceLocator. In the same di package, create a new file named
ActivityServiceLocator.kt and enter the following code:
// 1
const val NAVIGATOR = "Navigator"
// 2
val activityServiceLocatorFactory:
ServiceLocatorFactory<AppCompatActivity> =
{ activity: AppCompatActivity ->
ActivityServiceLocator(activity) }
class ActivityServiceLocator(
// 3
val activity: AppCompatActivity
) : ServiceLocator {
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
override fun <A : Any> lookUp(name: String): A = when (name) {
// 4
NAVIGATOR -> NavigatorImpl(activity)
else -> throw IllegalArgumentException("No component lookup
for the key: $name")
} as A
}
raywenderlich.com 106
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
4. Add the case for Navigator for the given constant, returning an instance of
NavigatorImpl that uses the Activity you pass in the primary constructor of
AppCompatActivity.
Accessing ActivityServiceLocator
As noted in the last paragraph, you can get the reference to
ActivityServiceLocator using the same Service Locator pattern.
Open ServiceLocatorImpl.kt and replace its content with the following code:
class ServiceLocatorImpl(
val context: Context
) : ServiceLocator {
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
override fun <A : Any> lookUp(name: String): A = when (name) {
LOCATION_OBSERVABLE -> locationObservable
// 2
ACTIVITY_LOCATOR_FACTORY -> activityServiceLocatorFactory
else -> throw IllegalArgumentException("No component lookup
for the key: $name")
} as A
}
raywenderlich.com 107
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
Here you:
Using ActivityServiceLocator
For your last step, you need to use ActivityServiceLocator. Open
SplashActivity.kt and apply the following changes:
// ...
private val handler = Handler()
private val disposables = CompositeDisposable()
private lateinit var locationObservable:
Observable<LocationEvent>
// 1
private lateinit var activityServiceLocator: ServiceLocator
private lateinit var navigator: Navigator
lookUp<ServiceLocatorFactory<AppCompatActivity>>(ACTIVITY_LOCATO
R_FACTORY)
.invoke(this)
// 3
navigator = activityServiceLocator.lookUp(NAVIGATOR)
}
// ...
2. Initialize activityServiceLocator with the object you get from the global
ServiceLocator, using the ACTIVITY_LOCATOR_FACTORY key.
3. Use the activityServiceLocator, using the NAVIGATOR key to get the reference
to the Navigator implementation.
raywenderlich.com 108
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
Now you can build and run Busso and see that everything works, as shown in Figure
4.3:
raywenderlich.com 109
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
• You create the ActivityServiceLocator instance through a factory you get from
a lookup on the ServiceLocatorImpl. In short, you need ServiceLocatorImpl to
create an ActivityServiceLocator to look up the Navigator implementation.
You can describe the logic better by using a sequence diagram, like the one in
Figure 4.5.
As you see, there’s some sort of dependency between the different ServiceLocator
implementations. The good news is that this is something you can improve, making
the code much simpler. You’ll do that in the next section.
raywenderlich.com 110
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
ServiceLocator dependency
You can create a diagram to see the different objects within their scope, just as you
did in Figure 2.14 of Chapter 2, “Meet the Busso App”. In this case, the result is the
following:
What isn’t obvious here is the relationship between the objects in the two different
scopes that you represented using the sequence diagram in Figure 4.5. That diagram
is just the representation of the following lines of code in onCreate() in
SplashActivity.kt:
// ...
// 1
locationObservable = lookUp(LOCATION_OBSERVABLE)
// 2
activityServiceLocator =
lookUp<ServiceLocatorFactory<AppCompatActivity>>(ACTIVITY_LOCATO
R_FACTORY)
.invoke(this)
// 3
raywenderlich.com 111
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
navigator = activityServiceLocator.lookUp(NAVIGATOR)
// ...
Here you:
Now, you might wonder why you need two different ServiceLocator
implementations to execute basically the same operation: looking up the instance of
a class, given a name. Wouldn’t be useful to use a single ServiceLocator
implementation to handle the different scopes?
// ...
class ActivityServiceLocator(
val activity: AppCompatActivity
) : ServiceLocator {
// 1
var applicationServiceLocator: ServiceLocator? = null
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
override fun <A : Any> lookUp(name: String): A = when (name) {
NAVIGATOR -> NavigatorImpl(activity)
// 2
else -> applicationServiceLocator?.lookUp<A>(name)
?: throw IllegalArgumentException("No component lookup for
the key: $name")
} as A
}
raywenderlich.com 112
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
Here you:
With this simple change, you delegate the look-up of objects not present in the
current implementation to an optional ServiceLocator.
// 1
val activityServiceLocatorFactory: (ServiceLocator) ->
ServiceLocatorFactory<AppCompatActivity> =
// 2
{ fallbackServiceLocator: ServiceLocator ->
// 3
{ activity: AppCompatActivity ->
ActivityServiceLocator(activity).apply {
applicationServiceLocator = fallbackServiceLocator
}
}
}
This change isn’t obvious and requires some functional programming knowledge.
raywenderlich.com 113
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
// ...
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
override fun <A : Any> lookUp(name: String): A = when (name) {
LOCATION_OBSERVABLE -> locationObservable
ACTIVITY_LOCATOR_FACTORY ->
activityServiceLocatorFactory(this) // HERE
else -> throw IllegalArgumentException("No component lookup
for the key: $name")
} as A
// ...
lookUp<ServiceLocatorFactory<AppCompatActivity>>(ACTIVITY_LOCATO
R_FACTORY)
.invoke(this)
// 1
locationObservable =
activityServiceLocator.lookUp(LOCATION_OBSERVABLE)
// 2
navigator = activityServiceLocator.lookUp(NAVIGATOR)
}
With this code, you use the same ServiceLocator implementation to:
raywenderlich.com 114
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
As you saw earlier, this is dependency lookup. But how can you implement this as
actual dependency injection? Before proceeding to the next step, build and run
Busso and verify that everything works as expected.
interface Injector<A> {
fun inject(target: A)
}
raywenderlich.com 115
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
This is the interface of any object that can inject references into others. For example,
create a new file, SplashActivityInjector.kt in the di package and give it the
following code:
This is just a simple implementation for the Injector interface… but what should it
do? What do you need to implement the inject() operation?
// 1
object SplashActivityInjector : Injector<SplashActivity> {
override fun inject(target: SplashActivity) {
// 2
val activityServiceLocator =
target.lookUp<ServiceLocatorFactory<AppCompatActivity>>(ACTIVITY
_LOCATOR_FACTORY)
.invoke(target)
// 3
target.locationObservable =
activityServiceLocator.lookUp(LOCATION_OBSERVABLE) // ERROR
// 4
target.navigator =
activityServiceLocator.lookUp(NAVIGATOR) // ERROR
}
}
raywenderlich.com 116
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
Note: Constructor injection doesn’t have this problem because you pass the
value to the primary constructor when you create the instance of the
dependent object.
In this example, you need to change the code in SplashActivity.kt by removing the
private visibility modifier from locationObservable and navigator and removing
activityServiceLocator, like this:
// ...
lateinit var locationObservable: Observable<LocationEvent>
lateinit var navigator: Navigator
// ...
// ...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
makeFullScreen()
setContentView(R.layout.activity_splash)
SplashActivityInjector.inject(this) // HERE
}
// ...
raywenderlich.com 117
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
Now, everything compiles. Build and run Busso, getting what’s shown in Figure 4.8:
raywenderlich.com 118
Dagger by Tutorials Chapter 4: Dependency Injection & Scopes
Key points
• Not all the objects you look up using ServiceLocator have the same lifecycle.
• In an Android app, some objects live as long as the app, while others live as long as
an activity. There’s a lifecycle for each Android standard component. You can also
define your own.
• ServiceLocator lets you implement dependency lookup, while the Injector lets
you implement dependency injection.
You improved Busso’s code, focusing on SplashActivity. However, you can use the
same approach through all the app by also managing the fragment scope. Don’t
worry you’ll get there.
In the next chapter, you’ll solve another problem, testability, before starting your
journey to using Dagger and then Hilt.
raywenderlich.com 119
5 Chapter 5: Dependency
Injection & Testability
By Massimo Carli
In the previous chapters, you refactored the Busso App to introduce the concept of
dependency injection by implementing a ServiceLocator and an Injector. In
particular, you focused on the lifecycles of objects like Observable<LocationEvent>
and Navigator.
This has simplified the code a bit, but there’s still a lot of work to do. Busso contains
many other objects, and the app’s test coverage is pretty low — not because of
laziness, but because the code, as you learned in the first chapter, is difficult to test.
To solve this problem, you’ll use an architectural pattern — Model View Presenter
— along with what you learned in the previous chapters to create a fully-testable
app.
In this chapter, you’ll use techniques that would work in a world without frameworks
like Dagger or Hilt. Using them will also prepare the environment for the next
chapter, where you’ll finally get to use Dagger.
Note: In this chapter, you’ll prepare Busso for Dagger and, later, Hilt. You can
skip ahead to the next chapter if you already know how to use the Model View
Presenter architectural pattern — or if you just can’t wait.
raywenderlich.com 120
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
Note: To learn all about architectural patterns in Android, read our book,
Advanced Android App Architecture (https://www.raywenderlich.com/books/
advanced-android-app-architecture).
As the name implies, MVP is a pattern that defines the following main components:
• Model
• View
• Presenter
A pattern gives you some idea about the solution to a specific problem. Different
projects implement patterns in different ways. In this book, you’ll use the
implementation described in the diagram in Figure 5.1:
raywenderlich.com 121
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
Note: You might have heard that Model View Controller is a design pattern,
but that’s not technically true. Historically, the only design patterns are the
ones listed in the famous book, Design Patterns: Elements of Reusable Object-
Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson and John
Vlissides, also known as “The Gang Of Four”.
Next, you’ll take a closer look at each of the components that compose MVP.
Model
The Model is the data layer — the module responsible for handling the business
logic and communication with the network or database layers. In Figure 5.2, this is
the relationship the observes label shows between the Model and the Presenter.
The Model state changes in response to external events or events from the user. The
updates label shows that relationship.
raywenderlich.com 122
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
// 1
const val BUSSO_ENDPOINT = "BussoEndpoint"
const val LOCATION_OBSERVABLE = "LocationObservable"
const val ACTIVITY_LOCATOR_FACTORY = "ActivityLocatorFactory"
class ServiceLocatorImpl(
val context: Context
) : ServiceLocator {
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
override fun <A : Any> lookUp(name: String): A = when (name) {
// 2
LOCATION_OBSERVABLE -> locationObservable
// 3
BUSSO_ENDPOINT -> bussoEndpoint
ACTIVITY_LOCATOR_FACTORY ->
activityServiceLocatorFactory(this)
else -> throw IllegalArgumentException("No component lookup
for the key: $name")
} as A
}
raywenderlich.com 123
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
1. Have the constants for the name you’ll use to look up the BussoEndpoint and
the Observable<LocationEvent>.
It’s important to remember that these objects have the same lifecycle as the app.
Note: The interesting part about the test for BussoEndpoint is the
implementation of a mock for it, as you’ll see later in this chapter.
At this point, you should have a better understanding of the Model. Next, you’ll take
a deeper look at the View component.
raywenderlich.com 124
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
Figure 5.3 shows the relationship the View has with the Presenter, under the
updates label.
In some Model View Presenter implementations, the Presenter interacts with the
View through the ViewBinder abstraction. Using a ViewBinder has two main
advantages. It:
1. Decouples the Presenter from the actual View implementation, which is usually
an Activity or a Fragment.
2. Avoids using the name View, which is also the name of one of the most
important classes in Android. The View class is the base class for all the UI
Android widgets.
raywenderlich.com 125
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
For type safety, it’s useful to define an abstraction for the ViewBinder. In Figure 5.4,
you can see the ViewBinder interface from libs/mvp:
// 1
interface ViewBinder<V> {
// 2
fun init(rootView: V)
}
1. It’s a generic interface in the generic type variable V. This represents the type for
the actual View or for another object that lets the ViewBinder implementation
access all the UI components. Using generics allows you to avoid any
dependencies with the Android framework.
How do you implement the ViewBinder interface? Busso gives you a very good
opportunity to find out.
raywenderlich.com 126
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
Note: For this chapter, you’ll need to read the code of the existing Busso
project directly in Android Studio. Copying all the code into the chapter would
take too much space.
• onCreateView() is where you inflate the layout for Fragment and prepare the UI
to display the BusStop information.
• For user events, you need to manage the selection of a BusStop in the list to
navigate to the arrivals information.
Now, you have all the information you need to define the specific ViewBinder
interface for the BusStopFragment.
// 1
interface BusStopListViewBinder : ViewBinder<View> {
// 2
fun displayBusStopList(busStopList: List<BusStopViewModel>)
// 3
fun displayErrorMessage(msg: String)
// 4
interface BusStopItemSelectedListener {
// 5
fun onBusStopSelected(busStopViewModel: BusStopViewModel)
// 6
fun retry()
}
}
raywenderlich.com 127
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
4. To monitor the events the user can generate from the UI, you define the
BusStopItemSelectedListener. You implement this interface to observe the
BusStop a user selects.
6. You also provide retry(), which lets the user attempt to fetch the BusStop
information again if an error occurs.
Implementing BusStopListViewBinder
Your next goal is to move around some code to simplify BusStopFragment and make
your app easier to test.
raywenderlich.com 128
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
raywenderlich.com 129
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
The next step is to implement the function that updates the UI components.
2. Displays a snackbar with the error message you received as its parameter.
This is quite straightforward. But you still need to handle the events.
raywenderlich.com 130
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
To do this, you need to apply the following changes to the existing code for
BusStopListViewBinderImpl, leaving the rest as it is:
class BusStopListViewBinderImpl(
// 1
private val busStopItemSelectedListener:
BusStopListViewBinder.BusStopItemSelectedListener? = null
) : BusStopListViewBinder {
busStopItemSelectedListener?.onBusStopSelected(selectedItem)
}
})
initRecyclerView(busStopRecyclerView)
}
// ...
raywenderlich.com 131
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
3. Add an action to the Snackbar that displays the error message. When the user
selects the action, you invoke the retry() callback operation on the
BusStopListViewBinder.BusStopItemSelectedListener.
Great job! You’ve just made a big improvement by implementing a ViewBinder for
the BusStopFragment. You did this for a reason: testing! Next, you’ll put that test
into place.
Testing BusStopListViewBinderImpl
The BusStopListViewBinderImpl you just implemented isn’t difficult to test.
Create the test class with Android Studio, just as you learned in Chapter 2, “Meet the
Busso App”, and add the following code:
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class BusStopListViewBinderImplTest {
@Before
fun setUp() {
activityController = Robolectric.buildActivity(
Activity::class.java
)
testData = createTestData()
fakeBusStopItemSelectedListener =
FakeBusStopItemSelectedListener()
busStopListViewBinder =
BusStopListViewBinderImpl(fakeBusStopItemSelectedListener)
}
// 1
raywenderlich.com 132
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
@Test
fun displayBusStopList_whenInvoked_adapterContainsData() {
val rootView = createLayoutForTest(activityController.get())
with(busStopListViewBinder) {
init(rootView)
displayBusStopList(testData)
}
val adapter =
rootView.findViewById<RecyclerView>(R.id.busstop_recyclerview).a
dapter!!
assertEquals(3, adapter.itemCount)
}
// 2
@Test
fun
busStopItemSelectedListener_whenBusStopSelected_onBusStopSelecte
dIsInvoked() {
val testData = createTestData()
val activity = activityController.get()
val rootView = createLayoutForTest(activity)
activity.setContentView(rootView)
activityController.create().start().visible();
with(busStopListViewBinder) {
init(rootView)
displayBusStopList(testData)
}
rootView.findViewById<RecyclerView>(R.id.busstop_recyclerview).g
etChildAt(2).performClick()
assertEquals(testData[2],
fakeBusStopItemSelectedListener.onBusStopSelectedInvokedWith)
}
raywenderlich.com 133
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
createBusStopViewModelForTest("3"),
)
Aside from a lot of scaffolding, this class allows you to test that when:
1. You invoke the displayBusStopList(), the app displays the data you pass as a
parameter in a RecyclerView.
These tests use Roboletric, which is outside the scope of this book, but it’s good to
prove that BusStopListViewBinderImpl contains code you can simply test in
isolation.
At this point, Busso has a Model and a ViewBinder but you still need to connect all
the dots. To do this you need a Presenter — a kind of mediator between the Model
and the View. You’ll learn about Presenters next.
raywenderlich.com 134
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
Presenter
As a mediator, the Presenter has two jobs. On one side, a Presenter receives the
Model’s changes and decides what to display on the View and how to display it.
On the other side, the Presenter receives user events from the View and decides how
to change the Model accordingly.
You can abstract the Presenter in different ways. Open Presenter.kt into the libs/
mvp module, as shown in Figure 5.5.
// 1
interface Presenter<V, VB : ViewBinder<V>> {
// 2
fun bind(viewBinder: VB)
// 3
fun unbind()
}
1. It’s a generic interface in the generic type variables V and VB. V is related to the
View and VB to the ViewBinder, which has to be related to the same type V.
raywenderlich.com 135
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
3. unbind() is the symmetric function you invoke to unbind the ViewBinder from
the Presenter. You also have the opportunity to release some resources here. In
RxJava, this would be where you’d dispose of the subscriptions to some
observables.
// 1
abstract class BasePresenter<V, VB : ViewBinder<V>> :
Presenter<V, VB> {
// 2
private var viewBinder: VB? = null
// 3
@CallSuper
override fun bind(viewBinder: VB) {
this.viewBinder = viewBinder
}
// 4
@CallSuper
override fun unbind() {
viewBinder = null
}
raywenderlich.com 136
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
// 5
protected fun useViewBinder(consumer: VB.() -> Unit) {
viewBinder?.run {
consumer.invoke(this)
}
}
}
3. Implement the bind() operation, saving the reference to the ViewBinder, which
you receive as a parameter, to the private viewBinder property. @CallSuper
forces the realizations of the BasePresenter to call the same operation on super
when overriding the bind() operation. This makes the initialization of the
viewBinder property safe.
Now, you have everything you need to implement the Presenter for the
BusStopFragment class.
// 1
interface BusStopListPresenter : Presenter<View,
BusStopListViewBinder>,
BusStopListViewBinder.BusStopItemSelectedListener {
// 2
fun start()
fun stop()
}
raywenderlich.com 137
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
2. You define two functions, start() and stop(), which allow you to bind the
BusStopListPresenter to the lifecycle of an Android component. In this case,
you bind it to the BusStopFragment.
3. React to the selection of a BusStop on the list and use the Navigator to get to
the next screen with the list of arrival times.
4. In case of error, use the BusStopListViewBinder to notify the user and manage
the retry() option.
• Navigator
• Observable
• BussoEndpoint
• BusStopListViewBinder
raywenderlich.com 138
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
class BusStopListPresenterImpl(
// 1
private val navigator: Navigator,
private val locationObservable: Observable<LocationEvent>,
private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusStopListViewBinder>(),
BusStopListPresenter {
// 2
override fun start() {
disposables.add(
locationObservable
.filter(::isLocationEvent)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(::handleLocationEvent, ::handleError)
)
}
raywenderlich.com 139
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
// 3
override fun stop() {
disposables.clear()
}
2. When the app invokes start(), you start observing the Observable
<LocationEvent>, just as the BusStopFragment did in the starter project.
raywenderlich.com 140
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
The remaining code is mostly the same as what you previously had in
BusStopFragment, except for the access to the BusStopListViewBinder, which now
uses useViewBinder(). The big difference is that now, the code is much simpler to
test. You’ll see that for yourself in the next step.
Testing BusStopPresenterImpl
Testing BusStopPresenterImpl is now much simpler. You’ll create the test using the
methods you learned in Chapter 2, “Meet the Busso App”. To start, enter the
following code:
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class BusStopListPresenterImplTest {
@Before
fun setUp() {
navigator = mock(Navigator::class.java)
locationObservable = PublishSubject.create();
bussoEndpoint = mock(BussoEndpoint::class.java)
busStopListViewBinder =
mock(BusStopListViewBinder::class.java)
presenter = BusStopListPresenterImpl(
navigator,
locationObservable,
bussoEndpoint,
)
presenter.bind(busStopListViewBinder)
}
@Test
fun
start_whenLocationNotAvailable_displayErrorMessageInvoked() {
presenter.start()
locationObservable.onNext(LocationNotAvailable("Provider"))
verify(busStopListViewBinder).displayErrorMessage("Location
Not Available")
}
}
The test in this code allow you to verify that, when Observable<LocationEvent>
emits a LocationNotAvailable event, BusStopListPresenterImpl sends a
Location Not Available error message to the BusStopListViewBinder.
raywenderlich.com 141
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
// ...
class FragmentServiceLocator(
val fragment: Fragment
raywenderlich.com 142
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
) : ServiceLocator {
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
override fun <A : Any> lookUp(name: String): A = when (name) {
BUSSTOP_LIST_PRESENTER -> {
// 1
if (busStopListPresenter == null) {
// 2
val navigator: Navigator =
activityServiceLocator!!.lookUp(NAVIGATOR)
// 2
val locationObservable: Observable<LocationEvent> =
activityServiceLocator!!.lookUp(
LOCATION_OBSERVABLE
)
// 2
val bussoEndpoint: BussoEndpoint =
activityServiceLocator!!.lookUp(BUSSO_ENDPOINT)
busStopListPresenter = BusStopListPresenterImpl(
navigator,
locationObservable,
bussoEndpoint
)
}
busStopListPresenter
}
BUSSTOP_LIST_VIEWBINDER -> {
// 1
if (busStopListViewBinder == null) {
// 2
val busStopListPresenter: BusStopListPresenter =
lookUp(BUSSTOP_LIST_PRESENTER)
busStopListViewBinder =
BusStopListViewBinderImpl(busStopListPresenter)
}
busStopListViewBinder
}
else -> activityServiceLocator?.lookUp<A>(name)
?: throw IllegalArgumentException("No component lookup for
the key: $name")
} as A
}
raywenderlich.com 143
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
2. You use the ServiceLocator to look up the dependencies for the objects you’re
providing.
raywenderlich.com 144
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
}
super.onStop()
}
}
Extending BusStopFragmentInjector
The very last step is to implement BusStopFragmentInjector. Open
BusStopFragmentInjector.kt and replace the existing code with the following:
parentActivity.lookUp<ServiceLocatorFactory<AppCompatActivity>>(
ACTIVITY_LOCATOR_FACTORY)
.invoke(parentActivity)
val fragmentServiceLocator =
activityServiceLocator.lookUp<ServiceLocatorFactory<Fragment>>(F
RAGMENT_LOCATOR_FACTORY)
.invoke(target)
with(target) {
// HERE
busStopListPresenter =
fragmentServiceLocator.lookUp(BUSSTOP_LIST_PRESENTER)
busStopListViewBinder =
fragmentServiceLocator.lookUp(BUSSTOP_LIST_VIEWBINDER)
raywenderlich.com 145
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
}
}
}
And that’s it! Build and run the Busso App. Everything should work, and you’ll see
what’s shown in Figure 5.7:
raywenderlich.com 146
Dagger by Tutorials Chapter 5: Dependency Injection & Testability
Key points
• Using an architectural pattern like Model View Presenter is a fundamental step
toward the creation of a professional app.
• Model, View and Presenter allow the creation of classes that are easier to test.
• Using a ViewBinder allows you to decouple the presentation logic from the
specific Android component.
• The Presenter mediates between View and Model. It’s often bound to the
lifecycle of an Android standard component.
You’ve now written a lot of code using only the information about the dependencies
between the different components of the Busso App. But… do you really need to
write all this code? Since you only needed the information about dependencies,
would it be possible to somehow provide the same information and generate all the
code you need?
Welcome on board, you’re now ready to begin your journey to Dagger and Hilt!
raywenderlich.com 147
Section II: Introducing Dagger
In this section, you’ll learn what Dagger is, how it works, and how it slashes the
amount of code you need to write by hand when you implement dependency
injection in your app.
You’ll learn how to deal with constructor, field and method injection with Dagger,
how to simplify the implementation of @Module by using @Binds in cases when
you have abstraction and its implementation, and how to use @Singleton to solve a
very common problem.
raywenderlich.com 148
6 Chapter 6: Hello, Dagger
By Massimo Carli
In the first section of this book, you did a lot of work to understand some
fundamental concepts — and added many lines of code to the Busso App in the
process. You started with the concept of dependency. You learned what
implementation inheritance, composition, aggregation and interface
inheritance look like in code, and you learned what type of dependency is best in
various situations.
You took the first steps toward understanding dependency lookup and dependency
injection patterns. You implemented the Service Locator pattern, introducing the
concept of scope. Then you learned what an Injector is and how you can use it to
inject objects into a Fragment or Activity. Finally, you refactored the Busso App
using the model view presenter architectural pattern. Now, Busso is much easier to
test and simpler to change.
Yes. it’s been a lot of work! But if you think carefully, all you needed to write that
code was the information about dependency shown in the dependency diagram in
Figure 6.1:
raywenderlich.com 149
Dagger by Tutorials Chapter 6: Hello, Dagger
You might wonder, then, if there’s a way to generate all that code automatically.
Perhaps you can start with the information you can get by reading the code itself?
class Server() {
Is there a way to generate all the code to inject an instance of the Repository
implementation into the Server? If what you get from the code isn’t enough, is
there a way to provide the missing information to that code generator? The answer is
yes: Dagger!
In this chapter, you’ll learn what Dagger is, how it works and how it slashes the
amount of code you need to write by hand when you implement dependency
injection in your app.
What is Dagger?
Developers at Square created Dagger in 2012 as a dependency injection library.
Dagger is a code generation tool.
Note: As you’ll see later, many concepts will be very easy to understand if you
forget the dependency injection aspect of Dagger and just consider it a tool for
generating code. Dagger is smart enough to get some information from the
code itself. In other cases, it’ll need your help.
Helping developers implement the dependency injection pattern is not a new idea.
Before Dagger, tools like PicoContainer (http://picocontainer.com/) and Guice
(https://github.com/google/guice/wiki/GettingStarted) were already available.
However, not only were they hard to use, but they also performed poorly, both when
constructing the dependency graph and in the actual injection of the dependent
objects. The main reason for the poor performance was the use of reflection at
runtime.
raywenderlich.com 150
Dagger by Tutorials Chapter 6: Hello, Dagger
Reflection is a Java tool that lets you get information about the properties, super-
classes, implemented interfaces, methods of a class and more by parsing the source
code, bytecode or memory at runtime.
Reflection has a big problem: performance. Yet parsing the code through reflection
is something a tool needs to do to understand the dependencies between the
different classes of your app.
Square had the great idea of moving the code parsing before the compilation task by
using an annotation processor. The goal of this code generation task is to create
the code you execute to achieve dependency injection.
To understand what the build process is, do a simple experiment. Create a new Kotlin
project with IntelliJ — or simply open the one you can find in the material code for
this chapter named Empty — and select the build task from the Gradle window, as
in Figure 6.2:
raywenderlich.com 151
Dagger by Tutorials Chapter 6: Hello, Dagger
This is quite self-explanatory, and some of the tasks have been removed to save
space. Without going into too many details, you initially compile the source code
and create a jar archive with the classes files. Before you can generate source code
automatically, you need to add some more tasks including adding an annotation
processor.
1. Parsing the source code: Searching for custom annotations with data that
provides additional information about the code itself.
2. Generating source files: Using the information from the previous step to create
source files that the compiler will add to the existing ones.
To see how this works, you’ll create a very simple DI framework next.
For this example, the main things the framework needs to do are:
raywenderlich.com 152
Dagger by Tutorials Chapter 6: Hello, Dagger
Your framework may understand some of the information directly from the code, but
it’ll need you to give it some help by using annotations.
Your first step is to mark the repository property as an entry point for the Server
by using the @RayInject annotation, like this:
@RayDi
class Server() {
@RayInject
lateinit var repository: Repository
You also use the @RayDi annotation to tell RayDi that Server is a class you want to
add to the dependency graph for the app.
@RayBind(Repository::class)
class FakeRepository : Repository {
With this code, you tell the RayDi tool that whenever you want to inject an object of
type Repository, you need to create an instance of FakeRepository. Now, the
RayDi framework has all the information it needs to create the code for the
injection.
Next, you’ll learn how you can inject FakeRepository into Server’s repository.
raywenderlich.com 153
Dagger by Tutorials Chapter 6: Hello, Dagger
1. annotation
2. processor
3. app
Annotation
The annotation module contains the definitions of @RayDi, @RayInject and
@RayBind. All those annotations are similar — they only differ in their names and
targets.
// 1
@Retention(AnnotationRetention.SOURCE)
// 2
@Target(AnnotationTarget.CLASS)
raywenderlich.com 154
Dagger by Tutorials Chapter 6: Hello, Dagger
// 3
annotation class RayBind(val interfaceClazz: KClass<*>)
Here you:
1. Use @Retention to tell the compiler that the information about this annotation
should persist at the source code level so it can be removed at bytecode and
runtime. That’s because the compiler is the only one that will use the annotation.
2. Tell the compiler that it can only use @RayBind when it applies to a class. You
can’t apply that annotation to a field, property or any other construct. If you try,
you’ll get a compilation error.
interface RayDiObjectFactory<T> {
fun create(): T
}
Processor
The processor module contains code to handle annotation processing when
building the project.
Note: The description of the code in this module is outside the scope of this
chapter, but you can have a look if you’re interested.
repositories {
mavenCentral()
raywenderlich.com 155
Dagger by Tutorials Chapter 6: Hello, Dagger
}
// 2
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-
jdk8:$kotlin_version"
// 3
implementation project(':annotation')
// 4
implementation "com.squareup:kotlinpoet:$kotlinpoet_version"
// 5
implementation "com.google.auto.service:auto-service:
$auto_service_version"
kapt "com.google.auto.service:auto-service:
$auto_service_version"
}
1. You use the kotlin-kapt plugin, which lets you use the annotation processor in
Kotlin to add all the required tasks.
4. To generate the Kotlin code, you use Kotlin Poet. This is another framework from
Square. It makes it simpler to generate Kotlin code.
5. You also use the auto-service annotation processor to simplify the installation
of generic annotation processors, creating all the configuration files.
raywenderlich.com 156
Dagger by Tutorials Chapter 6: Hello, Dagger
To see what happens when you have this configuration, double-click the build task
for the app module shown in Figure 6.4:
// 1
> Task :annotation:compileKotlin
> Task :annotation:compileJava
> Task :annotation:classes
> Task :annotation:jar
// 2
> Task :processor:kaptGenerateStubsKotlin
> Task :processor:kaptKotlin
// 3
> Task :app:kaptGenerateStubsKotlin
> Task :app:kaptKotlin
> Task :app:compileKotlin
> Task :app:compileJava
> Task :app:classes
> Task :app:jar
> Task :app:assemble
> Task :app:testClasses
> Task :app:build
raywenderlich.com 157
Dagger by Tutorials Chapter 6: Hello, Dagger
1. The build for the annotation module is exactly the same as the Empty project’s
was.
Note: The code generation that the processor module executes is related to
the auto-service kapt dependency. The annotation processor logic for RayDi is
enabled in the app module, as you’ll see soon.
App
The app module contains the code for the app. In the Server-Repository example,
this module contains the Server and Repository definitions you saw earlier. Again,
take a closer look at build.gradle’s contents:
kotlin {
// 3
sourceSets.getByName("main") {
kotlin.srcDir("${buildDir.absolutePath}/generated/source/
kaptKotlin/")
}
}
dependencies {
implementation project(path: ':annotation')
// 4
raywenderlich.com 158
Dagger by Tutorials Chapter 6: Hello, Dagger
kapt project(':processor')
}
2. Here, you can see how to enable stub generation using generateStubs. The
default value is false because, as mentioned earlier, it has performance
implications.
4. The main thing to note here is that you can use kapt to enable the RayDi
annotation processing in the app module.
Next, you’ll build the project and generate the code using the RayDi annotation
processor — and see what happens.
Generating code
Build the RayDi app by selecting the build task from the Gradle window and double-
clicking the build option. Open build/generated/source/kaptKotlin/main, as
shown in Figure 6.5:
1. Repository_RayDiFactory
2. Server_RayDiFactory
These are the implementations of the RayDiObjectFactory<T> interface you saw
earlier for each class annotated with @RayDi or RayBind.
raywenderlich.com 159
Dagger by Tutorials Chapter 6: Hello, Dagger
Open the second one, Server_RayDiFactory.kt, and you’ll see the following code:
You can see that the Server_RayDiFactory creates an instance of the Server class.
In this case, the return type is Repository but the actual object you return is an
instance of FakeRepository. Using the @RayBind annotation you specified which
implementation of the interface you want to use.
class RayDiFactory {
@Suppress("UNCHECKED_CAST")
fun <T> get(type: KClass<out Any>): T {
val target = when (type) {
Server::class -> Server_RayDiFactory().create()
.apply {
repository = Repository_RayDiFactory().create()
} as T
Repository::class -> Repository_RayDiFactory().create() as
T
else -> throw IllegalStateException()
}
return target as T
}
}
RayDi generated all this code for you, and you can see how it also managed the
dependency of Server from the implementation of the Repository interface.
Now, have a look at Main.kt to check if the generated code works as expected:
fun main() {
val server: Server = RayDiFactory()
.get(Server::class)
server.receive(Data("Hello"))
}
raywenderlich.com 160
Dagger by Tutorials Chapter 6: Hello, Dagger
Build the app and run main() and you’ll see the following output:
Added Data(name=Hello)
This proves that Server has a reference to the correct implementation of the
Repository interface: FakeRepository.
Note: You can do a simple exercise. Just create a new implementation for the
Repository interface and use @RayBind to replace the actual instance the
Server uses.
Great job! You learned how to use an annotation processor to generate code.
Of course, RayDi is very simple and limited. What happens, for instance, if the
Server also has a dependency it declares using parameters in the primary
constructor? What if FakeRepository has other dependencies? How can you detect
cyclic dependencies? Handling these complications isn’t a simple job, but Dagger’s
here to help!
Note: If you’re wondering what happened to the Busso App, don’t worry.
You’ll migrate it to Dagger very soon. You just need some examples of how
Dagger works in simpler apps first.
raywenderlich.com 161
Dagger by Tutorials Chapter 6: Hello, Dagger
DaggerServerRepository
Use IntelliJ to open the DaggerServerRepository project from the starter folder in
the materials for this chapter. It’s a very simple project with the file structure shown
in Figure 6.6:
class Server() {
raywenderlich.com 162
Dagger by Tutorials Chapter 6: Hello, Dagger
This class has the same definition as the one in RayDi. You need to make
repository property of the Server class of an interface type to be able to easily
swap the implementations without changing the Server class. At the moment,
DaggerServerRepository only has pseudo-code in Main.kt:
fun main() {
// Get the reference to a Server instance
// Invoke the receive() method
}
For your first step, you need to add the dependency to Dagger.
Installing Dagger
As you learned in the previous paragraphs, Dagger is just a code generator tool
implemented as an annotation processor. Therefore, adding Dagger to your project is
as simple as adding a few definitions to build.gradle for your project’s module.
raywenderlich.com 163
Dagger by Tutorials Chapter 6: Hello, Dagger
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.4.10'
// 1
id "org.jetbrains.kotlin.kapt" version "1.4.10"
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
// 2
implementation "com.google.dagger:dagger:2.28"
// 3
kapt "com.google.dagger:dagger-compiler:2.28"
}
After your work with the RayDi project, this should look very familiar. In this code,
you:
1. Install the kotlin-kapt plugin. The syntax might look different from the previous
case, but it’s simply another way to install a gradle plugin given its ID and
version.
2. Add the dependency to the dagger library. The current version at the time of
writing is 2.28, but that will most likely change.
3. Install the Dagger annotation processor using the kapt dependency handler.
Look for an icon like the one in Figure 6.8 in the top-right of the IDE:
raywenderlich.com 164
Dagger by Tutorials Chapter 6: Hello, Dagger
@Component
In the Server-Repository example with RayDi, you generated RayDiFactory as the
factory for all the components of the dependency graph. Dagger generates an
equivalent class by defining a @Component.
Note: In this book, you’ll call any interface annotated with the @Component
annotation @Component. The same will happen with other types of
annotations.
For Dagger to be able to provide a reference to an instance of the Server class, you
just need to create a simple interface. Do this by creating a new package named di.
Inside, create a new file named AppComponent.kt. Finally, copy the following code
into the new file:
// 1
@Component
// 2
interface AppComponent {
// 3
fun server(): Server
}
This is a very simple but important interface with many interesting things to note:
1. It must contain @Component, which you use to define classes with factory
responsibility, just as you did with RayDiFactory in the RayDi example. In the
following chapter, you’ll learn more about this fundamental annotation.
2. When you create a @Component, you don’t need to define any concrete class — an
interface is enough. This is a way to tell Dagger that you just want to get an
instance of a given type, along with all its dependencies, without knowing the
details about how Dagger does its job.
3. The return type of the operation you define in the @Component interface is the
only thing that really matters. The only reason the name of the operation is
important is to make your code more readable.
raywenderlich.com 165
Dagger by Tutorials Chapter 6: Hello, Dagger
Believe it or not, with this simple definition, you’ve started to talk to Dagger, telling
it that you need an instance of a Server. But at this point, when you build the app,
you’ll get an error with the following message:
[Dagger/MissingBinding]
com.raywenderlich.android.daggerserverrepository.Server cannot
be provided without an @Inject constructor or an @Provides-
annotated method.
public abstract interface AppComponent {
Dagger is just a generator tool, remember? You asked it to create a Server, but it
doesn’t know how to. You need to give Dagger more information, and you’ll find out
how to do so next.
@Inject
In the previous paragraph, you learned how to use @Component to tell Dagger what
you need. You asked for a Server, but Dagger doesn’t know how to create one. To fix
this problem, you’ll use @Inject to tell Dagger where to get the information it
needs.
Unlike @Component, @Inject isn’t something Dagger created. Instead, it’s part of
Java Specification Request 300.
In this case, you’ll use @Inject to tell Dagger how to create an instance of Server.
Start by opening Server.kt and annotating Server, as in the following code:
raywenderlich.com 166
Dagger by Tutorials Chapter 6: Hello, Dagger
Now you can build the app and verify that the error disappeared and everything is
successful. But what did Dagger generate? And how can you use it? You’ll look at
that next.
• Server_Factory.class
• DaggerAppComponent.class
It’s important to note that these are both Java classes. Server_Factory is very
similar to RayDiObjectFactory from the RayDi app. DaggerAppComponent is quite a
lot like RayDiFactory.
In this case, it’s also important to note how the name DaggerAppComponent is the
concatenation of the prefix Dagger and the name of the @Component you created
earlier.
Now that Dagger has created these classes, you just need to use them.
raywenderlich.com 167
Dagger by Tutorials Chapter 6: Hello, Dagger
Open Main.kt and replace the existing code with the following:
fun main() {
// 1
val server = DaggerAppComponent
.create()
.server()
// 2
server.receive(Data("Hello"))
}
Of course! Nobody told Dagger to inject the FakeRepository into the Server
instance it provides. How would it know?
You’ll solve the problem using @Inject. Open Server and add @Inject, like this:
@Inject // HERE
lateinit var repository: FakeRepository
raywenderlich.com 168
Dagger by Tutorials Chapter 6: Hello, Dagger
}
}
But how can Dagger create the instance of FakeRepository? The answer is easy: Use
@Inject.
Now, you can finally build and run the app. You’ll get the following output:
Added Data(name=Hello)
Congratulations! You just ran your first app using Dagger. There’s still a lot to learn
in the following chapters, but before that, there’s still something you need to do.
@Inject
lateinit var repository: Repository // HERE
raywenderlich.com 169
Dagger by Tutorials Chapter 6: Hello, Dagger
Build now and you’ll see the same error you got before:
error: [Dagger/MissingBinding]
com.raywenderlich.android.daggerserverrepository.Repository
cannot be provided without an @Provides-annotated method.
public abstract interface AppComponent {
In the following chapters, you’ll see how to provide this information in many
different ways. At the moment, the simplest way is to define a @Module.
Create a new file named MainModule.kt in the di module and enter the following
code:
// 1
@Module
object MainModule {
// 2
@Provides
fun provideRepository(): Repository = FakeRepository()
}
Here, it’s important to note that Dagger is no longer responsible for creating the
instance of FakeRepository. You do that in the provideRepository() function
implementation instead.
raywenderlich.com 170
Dagger by Tutorials Chapter 6: Hello, Dagger
For this reason, you don’t need @Inject in FakeRepository anymore. Open
FakeRepository.kt and remove the annotation, getting the following code:
The simple definition of @Module is not enough. You still need to tell Dagger which
@Component uses it.
AppComponent is the object that provides you the reference to the Server instance
with all its dependencies. It needs to know how to create the object of type
Repository to inject.
Using the modules attribute of @Component, you tell Dagger where to find the
information it needs: MainModule.
Now, build the code and run main(). You’ll get the following successful output:
Added Data(name=Hello)
Congratulations, you’ve now used Dagger to achieve the same result you got with the
RayDi annotation processor. But this is just the beginning of what you can do with
Dagger!
raywenderlich.com 171
Dagger by Tutorials Chapter 6: Hello, Dagger
Key points
• Dagger is just a code generation tool, helping you implement the dependency
injection pattern in your project.
• Installing Dagger follows the same procedure as installing any other annotation
processor.
• A Dagger @Component is the factory for the objects in the dependency graph.
• You can use @Inject to tell Dagger how to create an instance of a class.
• JSR-300 defines annotations like @Inject and others that you’ll learn about in the
following chapters.
Congratulations! In this chapter, you learned how Dagger works and what the main
annotations to implement the dependency injection pattern in your app are. You also
had the chance to learn how an annotation processor works and why it’s a very
powerful tool.
This is just the beginning — there’s much more to learn. In the next chapter, you’ll
cover more details about how Dagger implements the different types of injections
and get to know some utilities that help improve the performance of your app. See
you there!
raywenderlich.com 172
7 Chapter 7: More About
Injection
By Massimo Carli
In the previous chapter, you started using Dagger with a very basic Server-
Repository example. As you remember from the first chapter, the code you
implemented uses a simple dependency between the Server and a Repository
called loosely coupled. You represent this dependency with the UML diagram in
Figure 7.1:
raywenderlich.com 173
Dagger by Tutorials Chapter 7: More About Injection
You learned that the @Inject, @Component, @Module and @Provides annotations are
all you need to implement dependency injection in your app with Dagger. The rest of
the annotations let you improve performance when generating and executing the
code.
In this chapter, you’ll discover even more about dependency injection with Dagger.
You’ll learn how to:
• Simplify the implementation of @Module by using @Binds in cases when you have
an abstraction and its implementation. You saw how this works in the Repository
and FakeRepository example.
• Use @Singleton for the first time to solve a very common problem.
raywenderlich.com 174
Dagger by Tutorials Chapter 7: More About Injection
Getting started
In the previous chapter, you learned how to use some Dagger annotations in a Kotlin
project in IntelliJ. In this chapter, you’ll return to Android with the RaySequence
app. This is a very simple app that allows you to display a numeric value of a
sequence on the screen every time you press a Button.
To get started, use Android Studio to open the RaySequence project in the starter
folder of the materials for this chapter. Build and run and you’ll get the screen shown
in Figure 7.2:
Note: Don’t worry about the Busso App. In a few chapters, you’ll migrate it to
Dagger and everything will seem very easy to you.
raywenderlich.com 175
Dagger by Tutorials Chapter 7: More About Injection
At the moment, the app doesn’t work: When you click the Button, nothing happens.
Figure 7.3 shows the file structure of the app:
Before doing this, take a quick look at the model. Open SequenceGenerator.kt in
the model package of the app module and look at the following code:
interface SequenceGenerator<T> {
fun next(): T
}
raywenderlich.com 176
Dagger by Tutorials Chapter 7: More About Injection
Note: The Kotlin standard library already provides the Sequence<T> interface,
which is similar to SequenceGenerator<T> and has some utility builders like
the sequence() higher-order function. However, using Sequence<T> requires
you to define an Interator<T>, which makes the code a bit more complex
with no gain in the context of dependency injection.
class NaturalSequenceGenerator(
private var start: Int
) : SequenceGenerator<Int> {
override fun next(): Int = start++
}
In the code for the test build type, you’ll also find some unit tests.
The gradle.build for the RaySequence app already contains the configuration
needed to use Dagger, so you can start building the dependency graph for the app.
Note: To save space, some of the code for this project isn’t printed out. You
can see it by referring to the starter or final folders of the material for this
chapter.
raywenderlich.com 177
Dagger by Tutorials Chapter 7: More About Injection
• SequenceViewBinder
• SequencePresenter
• MainActivity
Finally, you’ll provide all the information Dagger needs to make RaySequence work.
class SequenceViewBinderImpl(
// HERE
private val sequenceViewListener:
SequenceViewBinder.Listener
) : SequenceViewBinder {
private lateinit var output: TextView
raywenderlich.com 178
Dagger by Tutorials Chapter 7: More About Injection
Both solutions require you to define a @Module because you want to use
SequenceViewBinder as the type of the instance in the dependency.
Whichever method you choose, the first step is the same: Create a new di package
with a new file named AppModule.kt in it. What you put inside depends on the
approach you decide to take.
// 1
@Module
object AppModule {
// 2
@Provides
fun provideSequenceViewBinder(
// 3
viewBinderListener: SequenceViewBinder.Listener
// 4
): SequenceViewBinder =
SequenceViewBinderImpl(viewBinderListener)
}
1. You define a @Module to provide Dagger with some of the information it needs to
build the dependency graph for the RaySequence app.
2. Using @Provides, you tell Dagger which function to invoke to provide the objects
of type SequenceViewBinder. The return type of the function is what matters
here.
raywenderlich.com 179
Dagger by Tutorials Chapter 7: More About Injection
To accomplish the first task, replace the contents of AppModule.kt with the
following code:
// 1
@Module
object AppModule {
// 2
@Module
interface Bindings {
// 3
@Binds
fun bindSequenceViewBinder(impl: SequenceViewBinderImpl):
SequenceViewBinder
}
}
As you can see, there’s less code here than in the previous case. Here you:
1. Define a @Module to give Dagger some of the information it needs to build the
dependency graph, as in the previous case.
raywenderlich.com 180
Dagger by Tutorials Chapter 7: More About Injection
2. Create a Bindings interface annotated as @Module that will contain all the
binding definitions.
3. Use @Binds to bind the type of abstraction to the implementation to use. The
type of the abstraction is the return type of the binding function. The type of
the implementation is the type of the unique parameter for the same function.
In this case, you’re telling Dagger that whenever it needs an object of type
SequenceViewBinder, it needs to return an instance of
SequenceViewBinderImpl.
Note: Using an internal interface for the definition of @Binds is just the
convention this book uses. It’s a simple way to keep the concrete @Provides
functions in one concrete object and the abstract @Binds functions in another,
but still in the same file. However, you’ll see other conventions in your future
work with Dagger.
It’s interesting to note that, right now, you can build the app with no errors. That’s
because you don’t have any @Component yet so Dagger doesn’t have anything to do.
raywenderlich.com 181
Dagger by Tutorials Chapter 7: More About Injection
Inside SequencePresenter.kt, you’ll find this code, which tells you that
SequencePresenter IS-A SequenceViewBinder.Listener.
interface SequencePresenter :
Presenter<MainActivity, SequenceViewBinder>,
SequenceViewBinder.Listener { // HERE
fun displayNextValue()
}
raywenderlich.com 182
Dagger by Tutorials Chapter 7: More About Injection
You can represent these relations using the UML diagram in Figure 7.4:
You already know how to do all this. You’ll put that knowledge to work next.
raywenderlich.com 183
Dagger by Tutorials Chapter 7: More About Injection
@Module
object AppModule {
@Module
interface Bindings {
// ...
@Binds
fun bindSequencePresenter(impl: SequencePresenterImpl):
SequencePresenter // HERE
}
}
// 1
class SequencePresenterImpl @Inject constructor(
) : BasePresenter<MainActivity,
SequenceViewBinder>(),
SequencePresenter {
// 2
@Inject
lateinit var sequenceModel: SequenceGenerator<Int>
// ...
}
raywenderlich.com 184
Dagger by Tutorials Chapter 7: More About Injection
@Module
object AppModule {
// ...
@Provides
fun provideSequenceGenerator(): SequenceGenerator<Int> =
NaturalSequenceGenerator(0)
// ...
}
As you saw earlier, this is the Presenter and it must be the same instance of the
SequencePresenter implementation that you defined in the previous paragraphs.
Because you’re binding a class to an abstraction, you can just add the following code
to AppModule.kt:
@Module
object AppModule {
// ...
@Module
interface Bindings {
// ...
// 1
@Binds
// 2
fun bindViewBinderListener(impl: SequencePresenter):
// 3
SequenceViewBinder.Listener
}
}
raywenderlich.com 185
Dagger by Tutorials Chapter 7: More About Injection
MainActivity
What you’ve done so far is really cool, but you still need to apply that work to
RaySequence.
As you learned in the previous chapter, when you build the app, the annotation
processor creates some code for you. So far, Dagger hasn’t done anything — it can’t
until you create a @Component to use as the factory for the required instances. In
RaySequence’s case, you need a Presenter and a ViewBinder. You’ll handle that
next.
// 4
override fun onStart() {
super.onStart()
presenter.bind(viewBinder)
}
raywenderlich.com 186
Dagger by Tutorials Chapter 7: More About Injection
// 5
override fun onStop() {
presenter.unbind()
super.onStop()
}
}
5. Unbind the viewBinder from the presenter using the unbind() operation
inherited from BasePresenter in the onStop() lifecycle function.
Build and run now… and it will crash. That’s because nobody initialized the
lateinit vars. To do this, you need a @Component.
Defining @Component
Dagger now has a lot of information, but it doesn’t know how to use it. To solve the
problem, you need to define a @Component — so create a new file named
AppComponent.kt in the di package and copy the following code into it:
// 1
@Component(modules = [
AppModule::class,
AppModule.Bindings::class
])
interface AppComponent {
// 2
fun viewBinder(): SequenceViewBinder
// 3
fun presenter(): SequencePresenter
}
1. A @Component to use as a factory for the instances of the dependency graph. You
use AppModule and AppModule.Bindings to get the binding information.
raywenderlich.com 187
Dagger by Tutorials Chapter 7: More About Injection
Once again, the return type of the operation you define is what’s important for
resolving the instances in the dependency graph.
Now, you can finally build the app and Dagger will generate some code for you. But
how can you use that code in MainActivity? You’ll see next
The simplest way of doing this is to open MainActivity.kt and change onCreate()
to the following:
raywenderlich.com 188
Dagger by Tutorials Chapter 7: More About Injection
If you don’t like how the injection happens here, don’t worry, you’ll improve the
code soon.
Now you can build and run, but the app still doesn’t work! It doesn’t crash, but when
you click on the button, nothing happens. Everything should be fine, so what’s
wrong?
As mentioned above, you didn’t use the best way to initialize presenter and
viewBinder, but that’s not what’s causing the problem.
Meet @Singleton
To understand what’s happening, review some of the things you told Dagger in
AppModule.kt. You told it to:
However, this doesn’t answer a simple question: Is the instance it returns in those
two cases the same instance? Or does Dagger create a new instance every time it
needs a reference to an object?
To answer this question, you’ll make a simple change in the code to add some logs.
Open SequenceViewBinderImpl.kt and add the following code to it:
raywenderlich.com 189
Dagger by Tutorials Chapter 7: More About Injection
// ...
override fun onCreate(savedInstanceState: Bundle?) {
DaggerAppComponent.create().apply {
presenter = presenter()
Log.d("DAGGER_LOG", "Presenter: $presenter") // HERE
viewBinder = viewBinder()
}
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewBinder.init(this)
}
// ...
}
This prints the reference to the instance of SequencePresenter that Dagger injects
into MainActivity.
Now, build and run and use the filtering option in the LogCat window in Android
Studio to see what’s happening, as in Figure 7.5:
This means that, as things now stand, Dagger creates a new instance of a class every
time it needs a reference to an object of the related type. In most cases you don’t
need a new instance every time, you can use the same instance each time it’s
injected.
raywenderlich.com 190
Dagger by Tutorials Chapter 7: More About Injection
How can you fix that? This is a good opportunity to introduce the @Singleton
annotation.
Using @Singleton
You already met the fundamental concept of scope in Chapter 4, “Dependency
Injection & Scopes”, and you’ll learn a lot more in the following chapters. However,
it’s very important to say: @Singleton is nothing special.
Take careful note: Any annotation you use as a scope annotation is a way to bind the
lifecycle of the objects handled by a specific @Component to the lifecycle of the
@Component itself.
You’ll read the previous statement many times in the following chapters because it’s
a fundamental concept you need to learn to master Dagger.
To see what this means, open SequencePresenterImpl.kt and replace the header of
the class with this:
@Singleton // HERE
class SequencePresenterImpl @Inject constructor(
) : BasePresenter<MainActivity,
SequenceViewBinder>(),
SequencePresenter {
// ...
}
Using @Singleton, you tell Dagger that a @Component should only create a single
instance of SequencePresenterImpl.
Build the app now, however, and Dagger will complain with the following error:
That’s because you didn’t tell Dagger everything! You told it to bind the instance of
SequencePresenterImpl to the instance of a component, but you didn’t tell which
@Component.
raywenderlich.com 191
Dagger by Tutorials Chapter 7: More About Injection
@Component(modules = [
AppModule::class,
AppModule.Bindings::class
])
@Singleton // HERE
interface AppComponent {
For objects with no scope, like SequenceViewBinder, Dagger will create a new
instance every time you need an object of that type. For objects annotated with
@Singleton, AppComponent will always return the same instance.
Build and run and check the log again. Now, you’ll see something like this:
raywenderlich.com 192
Dagger by Tutorials Chapter 7: More About Injection
As you see, you always use the same SequencePresenterImpl now. More
importantly, RaySequence works.
Open SequenceViewBinderImpl.kt and replace the first part of the code with the
following:
init {
Log.d("DAGGER_LOG", "Listener: $sequenceViewListener")
}
// 2
@Inject
fun configSequenceViewListener(listener:
raywenderlich.com 193
Dagger by Tutorials Chapter 7: More About Injection
SequenceViewBinder.Listener) {
sequenceViewListener = listener
}
// ...
}
Now:
Build and run and you’ll note that the app still works. You already learned when to
use method injection and what the possible advantages are in Chapter 3,
“Dependency Injection”. However, it’s useful to know that Dagger supports it, as
well.
When you have a deeper understanding of how @Components work, you’ll discover
different approaches for the injection. But after reading this chapter, you already
have all the information to implement something similar to the Injector<T> you
saw in Chapter 4, “Dependency Injection & Scopes”. You’ll do that next.
@Component(modules = [
AppModule::class,
AppModule.Bindings::class
])
@Singleton
interface AppComponent {
// HERE
fun inject(mainActivity: MainActivity)
}
With this code, you replaced the factory method for SequenceViewBinder and
SequencePresenter with a function that accepts MainActivity as the single
parameter.
raywenderlich.com 194
Dagger by Tutorials Chapter 7: More About Injection
Note: Using the name inject() for these functions is a convention. However,
nothing’s stopping you from picking another name, if you prefer.
Now, you need to utilize the code Dagger generates. Open MainActivity.kt and
replace the first part of the class with the following:
Here, you:
In this case, it’s important to note that what matters here is the type of the
parameter of the inject() operation. This cannot be a generic type, but must be
the explicit type of the object destination of the injection.
Build and run. Now, everything works as expected. You’ve seen that Dagger allows
you to define, declaratively, how to generate an Injector for a given class. Cool,
right? :]
raywenderlich.com 195
Dagger by Tutorials Chapter 7: More About Injection
Key points
• Dagger supports constructor, method and field injection.
• @Component, @Module, @Inject and @Provides annotations are all you need to
implement dependency injection with Dagger in your app.
• @Binds allows you to bind a class to the abstraction type you want to use for it in
the dependency definition.
• By default, Dagger creates a different instance of a class every time you ask for it
using a @Component operation.
• @Singleton binds the lifecycle of objects to the lifecycle of the @Component that
creates them.
Using the RaySequence app, you learned how to use the main Dagger annotations in
a simple model view presenter architecture. You learned how the @Module,
@Component, @Provides and @Inject annotations help you define the dependency
graph. Finally, you met the @Binds and @Singleton annotations for the first time
and used them to solve some very common problems.
There are many other scenarios that Dagger can help with, however. In the next
chapter, you’ll learn everything you need to know about the @Module annotation.
raywenderlich.com 196
8 Chapter 8: Working With
Modules
By Massimo Carli
But you can use @Modules for more than that. As the name implies, they’re also a way
to group definitions. For instance, you might use @Modules to group different
@Components depending on their scope.
Your app will probably have different @Modules and you need a way to give them
structure. @Modules can have dependencies as well.
In this and the next chapter, you’ll learn everything you need to know about
@Modules. In this chapter, you’ll learn how to:
raywenderlich.com 197
Dagger by Tutorials Chapter 8: Working With Modules
Note: In this chapter, you’ll continue working on the RaySequence app. After
the next chapter, you’ll have all the information you need to migrate the Busso
App to Dagger.
Throughout the chapter, you’ll change configurations often. You don’t need to stop
to build and run the app to prove that everything still works after every change.
Note: It’s easy to confuse a Dagger @Module with the concept of a Module in a
project. You’ll see a note like this when there’s possible ambiguity.
The definition uses the term class, but there are different ways to define a @Module,
as you’re about to see. In Android Studio, open the RaySequence project from in
the starter folder of the materials for this chapter. Now, open AppModule.kt in the
di package and look at the following code:
@Module
object AppModule {
@Provides
fun provideSequenceGenerator(): SequenceGenerator<Int> =
NaturalSequenceGenerator(0)
@Module
interface Bindings {
@Binds
fun bindSequenceViewBinder(impl: SequenceViewBinderImpl):
SequenceViewBinder
@Binds
fun bindSequencePresenter(impl: SequencePresenterImpl):
SequencePresenter
@Binds
raywenderlich.com 198
Dagger by Tutorials Chapter 8: Working With Modules
As mentioned in the previous chapter, this is just a way to define some @Binds and
@Provides functions in the same file. These are actually two different modules. You
can prove this by opening AppComponent.kt in the same di package:
@Component(
modules = [
AppModule::class,
AppModule.Bindings::class
]
)
@Singleton
interface AppComponent {
@Module has an includes attribute that allows you to do what the name says:
Including AppModule.Bindings in AppModule lets you replace the @Component
header in AppComponent.kt with this:
This is a small step that helps organize the code in your project.
raywenderlich.com 199
Dagger by Tutorials Chapter 8: Working With Modules
@Module
interface AppBindings {
@Binds
fun bindSequenceViewBinder(impl: SequenceViewBinderImpl):
SequenceViewBinder
@Binds
fun bindSequencePresenter(impl: SequencePresenterImpl):
SequencePresenter
@Binds
fun bindViewBinderListener(impl: SequencePresenter):
SequenceViewBinder.Listener
}
Now, open AppModule.kt and replace its code with the following:
@Provides
fun provideSequenceGenerator(): SequenceGenerator<Int> =
NaturalSequenceGenerator(0)
}
Here, you removed the internal Bindings and updated the value for includes. This
isn’t a big deal, but it allows you to explore other ways of defining a @Module.
@Module
abstract class AppBindings {
@Binds
abstract fun bindSequenceViewBinder(impl:
SequenceViewBinderImpl): SequenceViewBinder
raywenderlich.com 200
Dagger by Tutorials Chapter 8: Working With Modules
@Binds
abstract fun bindSequencePresenter(impl:
SequencePresenterImpl): SequencePresenter
@Binds
abstract fun bindViewBinderListener(impl: SequencePresenter):
SequenceViewBinder.Listener
}
Now the class is abstract, like @Binds are. So what influence does that have on the
code Dagger generates? None at all.
It’s easy to see that the code is the same. Just check what’s in build/generated/
source/kapt/debug in the app module for the two cases:
raywenderlich.com 201
Dagger by Tutorials Chapter 8: Working With Modules
@Module(includes = [AppBindings::class])
class AppModule { // HERE
@Provides
fun provideSequenceGenerator(): SequenceGenerator<Int> =
NaturalSequenceGenerator(0)
}
Build and run to see that everything’s fine but, this time, the code Dagger generated
is different. Remember that, at the moment, Dagger works in a Java environment so
what really matters is the Java equivalent of the code you write in Kotlin. Look at
build/generated/source/kapt/debug in the app module again, and you’ll see that
Dagger generates a file for each @Provides in the @Module.
The difference is that the object is basically a singleton: You already have one single
instance. With the class, you need to create at least one instance — but you could
create many.
raywenderlich.com 202
Dagger by Tutorials Chapter 8: Working With Modules
Open AppModule.kt with Android Studio and select Tools ▸ Kotlin ▸ Show Kotlin
Bytecode, as in Figure 8.3:
raywenderlich.com 203
Dagger by Tutorials Chapter 8: Working With Modules
You can ignore that bytecode and just select the Decompile button and you’ll get a
new source tag similar to the one in Figure 8.5:
When you use an object instead, AppModule’s code looks like this:
@Provides
@NotNull
public final SequenceGenerator provideSequenceGenerator() {
return (SequenceGenerator)(new NaturalSequenceGenerator(0));
}
raywenderlich.com 204
Dagger by Tutorials Chapter 8: Working With Modules
private AppModule() {
}
static {
AppModule var0 = new AppModule();
INSTANCE = var0;
}
}
In this code, you can recognize the implementation of the Singleton pattern. This is
the code Dagger processes to generate code. If you use a class, Dagger will create an
instance of AppModule to delegate the creation of the SequenceGenerator<T>
implementation. If you use an object, Dagger doesn’t create an instance, but uses
the existing one instead.
You can easily compare how Dagger generates the code in these two cases by looking
at the build folder.
In theory, you could make AppModule abstract. Try it out by changing the code of
AppModule.kt, like this:
@Module(includes = [AppBindings::class])
abstract class AppModule { // HERE
@Provides
fun provideSequenceGenerator(): SequenceGenerator<Int> =
NaturalSequenceGenerator(0)
}
Building the app now results in an error with the following message:
AppComponent.java:8: error:
com.raywenderlich.android.raysequence.di.AppModule is abstract
and has instance @Provides methods. Consider making the methods
static or including a non-abstract subclass of the module
instead.
raywenderlich.com 205
Dagger by Tutorials Chapter 8: Working With Modules
@Module(includes = [AppBindings::class])
abstract class AppModule { // 1
// 2
companion object {
// 3
@Provides
// 4
@JvmStatic
fun provideSequenceGenerator(): SequenceGenerator<Int> =
NaturalSequenceGenerator(0)
}
}
4. Use the Kotlin @JvmStatic annotation to tell the compiler that it should
generate static provideSequenceGenerator function in the enclosing
AppModule class.
Build and run to confirm that Dagger’s happy now and everything works.
raywenderlich.com 206
Dagger by Tutorials Chapter 8: Working With Modules
The cold start time is how long the app takes to start from scratch. This includes the
time used to load and launch the app, display a starting window, create the process
for the app, launch the main thread, create the main Activity, inflate the views, lay
out the screen and perform the initial draw. The cold start time should always be less
than two seconds.
Note: The following example is just a way to show how the Lazy<T> interface
works in Dagger. The best way to improve the cold start time is to run the
heavy code off the main thread, which is outside the scope of this book.
Suppose you want to simulate creating an object of the graph for the RaySequence
app that is expensive in terms of taking a lot of time to load. Open
NaturalSequenceGenerator.kt and add the init block with the following code:
Here, you added an init() block with a three-second sleep to simulate the lag from
creating an expensive object. Build and run and you’ll notice some delay, but it
would be better to have an objective measure of how long it takes.
How can you measure the cold start time for the RaySequence app? Since Android
4.4, this information is easy to get. Just look at the LogCat window in the bottom part
of Android Studio and filter the log using the text “Displayed”. Also, select the No
Filter option in the combo on the right, as in Figure 8.6
Figure 8.6 — Use LogCat to filter the Startup time for the app
You can’t build and run the app yet, however, because the Activity for the LAUNCH is
SplashActivity, which doesn’t contain @Component’s initialization. To help with
this, there’s already a build type named coldstart in the project.
raywenderlich.com 207
Dagger by Tutorials Chapter 8: Working With Modules
Now, open the Build Variant Window on the left side of Android Studio and select
coldstart for the app and mvp modules, as in Figure 8.7:
In this case, the cold start time is 3 seconds and 884 ms. Of course, this is due to
the sleep(3000) you introduced in the init block of NaturalSequenceGenerator.
So the model is slowing the app down at start time — but you don’t need that model
until you press the button on the screen. This means you could create
NaturalSequenceGenerator later, making the app start more quickly. Dagger allows
you to use the Lazy<T> interface to delay the creation of an object.
@Singleton
class SequencePresenterImpl @Inject constructor() :
BasePresenter<MainActivity, SequenceViewBinder>(),
SequencePresenter {
@Inject
lateinit var sequenceModel:
dagger.Lazy<SequenceGenerator<Int>> // 1
raywenderlich.com 208
Dagger by Tutorials Chapter 8: Working With Modules
// ...
}
Build and run and you’ll get a cold start time similar to the following:
Now, the cold start time is 799ms, much smaller than the previous start time. Of
course, you pay a price for this when you need the SequenceGenerator<Int>
implementation. The first time you click the button, you’ll notice the delay when
the model creation takes a while.
1. If you define a dependency type with Lazy<T>, you get a faster startup time for
free. You just need to provide the object of type T. Dagger applies the laziness
automatically.
2. You create the object the first time you invoke get() and, after that, you’ll get
always the same instance. However, Lazy<T> is not like @Singleton. The former
is an optimization, the latter is a matter of scope. You’ll learn more about this
later.
Lazy<T> is a good tool, but it’s not the solution to every problem.
Before you move on, remember to restore the current build type to debug and to
remove delay(3000) from NaturalSequenceGenerator.
raywenderlich.com 209
Dagger by Tutorials Chapter 8: Working With Modules
This is fine, but what if you want to manage the dependency between the presenter
and the viewBinder using Dagger? You’ll see how to do that next.
@Singleton
class CycledSequencePresenter @Inject constructor(
private val viewBinder: SequenceViewBinder // 1
) : SequencePresenter { // 2
@Inject
lateinit var sequenceModel: SequenceGenerator<Int>
raywenderlich.com 210
Dagger by Tutorials Chapter 8: Working With Modules
@Module
abstract class AppBindings {
// ...
@Binds
abstract fun bindSequencePresenter(impl:
CycledSequencePresenter): SequencePresenter
}
raywenderlich.com 211
Dagger by Tutorials Chapter 8: Working With Modules
You can read the stack trace in the error message or look at the UML diagram in
Figure 8.8 to find out:
You need to break this cycle. But how can you do that? Lazy<T> comes to the rescue.
@Singleton
class CycledSequencePresenter @Inject constructor(
private val viewBinder: dagger.Lazy<SequenceViewBinder> // 1
) : SequencePresenter {
override fun displayNextValue() {
viewBinder.get().showNextValue(sequenceModel.next()) // 2
}
// ...
}
raywenderlich.com 212
Dagger by Tutorials Chapter 8: Working With Modules
Now, you’ll be able to successfully build the project. When you try to run, however,
you get an error that you’ve already met before, in SequenceViewBinderImpl:
That’s because, when you press the button on the screen, Dagger creates the first
instance of the lazy SequenceViewBinder implementation — which is different from
the one it already injected into MainActivity.
@Inject
lateinit var presenter: SequencePresenter
@Inject
lateinit var viewBinder: dagger.Lazy<SequenceViewBinder>
In this case, the Lazy you define in the MainActivity differs from the one in
CycledSequencePresenter. In short, Dagger will create two distinct instances: one
for the MainActivity and one for the CycledSequencePresenter.
Before continuing, build and run to check that the app still crashes with the same
error.
raywenderlich.com 213
Dagger by Tutorials Chapter 8: Working With Modules
@Singleton // HERE
class SequenceViewBinderImpl @Inject constructor(
private var sequenceViewListener:
SequenceViewBinder.Listener,
private val context: Context
) : SequenceViewBinder {
// ...
}
Now you can build and run successfully — but there’s a but! You just used the Lazy
type to solve a problem that had nothing to do with performance. You needed to
break a cycle and not to defer the creation of an object. But there could be a better
solution: Provider<T>.
If you just need to defer something without the caching feature Lazy<T> provides,
Provider<T> is the interface for you.
@Singleton
class CycledSequencePresenter @Inject constructor(
private val viewBinder: Provider<SequenceViewBinder> // 1
) : SequencePresenter {
2. Didn’t change the way you get the reference to the provided instance. You still
use get().
raywenderlich.com 214
Dagger by Tutorials Chapter 8: Working With Modules
It’s worth mentioning that Provider<T> isn’t a Dagger interface. Like @Inject, it’s
part of Java Specification Request 330.
Now, when you build and run, everything works fine. But there’s something
important to mention. Open CycledSequencePresenter.kt and change
displayNextValue() like this:
@Singleton
class CycledSequencePresenter @Inject constructor(
private val viewBinder: Provider<SequenceViewBinder>
) : SequencePresenter {
override fun displayNextValue() {
val binder = viewBinder.get() // 1
Log.d("DAGGER_LOG", "Binder: $binder") // 2
binder.showNextValue(sequenceModel.next())
}
// ...
}
Here you:
Build and run the app, then click the Button and use *DAGGER_LOG as the filter.
You’ll get a log like this:
The object you get from Provider<T> is always the same — but this isn’t because of
Provider<T> itself, but because of the @Singleton annotation you used on
CycledSequencePresenter.
When you invoke get() on a Provider, Dagger resolves the object for the related
type. Take a quick moment to prove this. Create a new file named
RandomModule.kt in the di package and add the following code:
@Module
class RandomModule {
@Provides
fun provideRandomInt(): Int = Random.nextInt()
}
raywenderlich.com 215
Dagger by Tutorials Chapter 8: Working With Modules
@Component(modules = [
AppModule::class,
RandomModule::class // HERE
])
@Singleton
interface AppComponent {
// ...
}
@Singleton
class CycledSequencePresenter @Inject constructor(
private val viewBinder: Provider<SequenceViewBinder>,
private val randomProvider: Provider<Int> // HERE
) : SequencePresenter {
override fun displayNextValue() {
val binder = viewBinder.get()
Log.d("DAGGER_LOG", "Binder: $binder $
{randomProvider.get()}") // HERE
binder.showNextValue(sequenceModel.next())
}
// ...
}
Here you:
2. Invoke get() on the randomProvider every time you print a log message.
Now, build and run and click the Next button a few times. You’ll get an output
similar to this:
As you see, randomProvider provides a different value every time you invoke get().
As a good exercise, check what happens if you inject a Lazy<Int> instead. Moreover,
check what happens if you inject two different Lazy<Int>s. Enjoy!
raywenderlich.com 216
Dagger by Tutorials Chapter 8: Working With Modules
Key points
• A @Module can include other modules by using the includes attribute.
• Dagger parses the Java equivalent of your Kotlin to generate its code.
In this chapter, you’ve learned more about Dagger @Modules. You saw how to
structure different @Modules and how to use dagger.Lazy<T> and Provider<T>,
depending on the problem you want to solve. In the next chapter, you’ll dig in even
deeper and learn more about Dagger @Modules.
raywenderlich.com 217
9 Chapter 9: More About
Modules
By Massimo Carli
In the previous chapter, you learned some important concepts about Dagger
@Modules. You saw how to split the bindings for your app into multiple files using
abstract classes, objects, companion objects and interfaces. You learned when
and how to use the dagger.Lazy<T> interface to improve performance. And you
used the Provider<T> interface to break cycled dependencies.
• When to create custom qualifiers to make the code easier to read and less error-
prone.
• How Android Studio can help you navigate the dependency tree.
Note: In this chapter, you’ll continue working on the RaySequence app but by
the next one, you’ll have all the information you need to migrate the Busso
App to Dagger.
raywenderlich.com 218
Dagger by Tutorials Chapter 9: More About Modules
Open AppModule.kt and replace the existing code with the following:
@Module(includes = [AppBindings::class])
class AppModule {
@Provides
fun provideSequenceGenerator(): SequenceGenerator<Int> =
FibonacciSequenceGenerator()
}
You also know that you can provide an instance of the SequenceGenerator<Int>
implementation using @Binds. Replace the previous code with the following:
@Module(includes = [AppBindings::class])
interface AppModule {
@Binds
fun bindsSequenceGenerator(impl: FibonacciSequenceGenerator):
SequenceGenerator<Int>
}
raywenderlich.com 219
Dagger by Tutorials Chapter 9: More About Modules
By using @Binds in place of @Provides, you reduced the number of files from two to
one and the total number of lines of code from 102 to 62 — a reduction of about
40%! This has a great impact on your work: Fewer classes and lines of code mean
faster building time.
@Binds was added in Dagger 2.12 specifically to improve performance. So you might
wonder, why not always use @Binds? In theory, they’re the best choice, but:
• An @Provides method can have multiple parameters of any type and cannot be
abstract. A @Binds function must be abstract and can only have one parameter
that must be a realization of its return type.
• On the other hand, a @Provider can have some logic that decides which
implementation to use based on some parameter values.
These are all aspects you need to consider before choosing the best option for you.
raywenderlich.com 220
Dagger by Tutorials Chapter 9: More About Modules
1. Add a second parameter to the primary constructor. This is the reference to the
Android Context you use in showNextValue().
2. Use the context to get access to a resource to format the output on the screen.
If you build the project now, you’ll get an error. This is expected because you’re
delegating the creation of the instance of SequenceViewBinderImpl to Dagger, but
you didn’t tell it how to create the Context. How can you do that? One option is to
use @Module. But all your modules are interfaces now, and you need something more
concrete.
Create a new file named ContextModule.kt in the di package and enter the
following code:
@Module
class ContextModule(val context: Context) { // HERE
@Provides
fun provideContext(): Context = context
}
raywenderlich.com 221
Dagger by Tutorials Chapter 9: More About Modules
@Component(modules = [
AppModule::class,
ContextModule::class // HERE
])
@Singleton
interface AppComponent {
Build the app now and you’ll get the compilation error in Figure 9.3:
raywenderlich.com 222
Dagger by Tutorials Chapter 9: More About Modules
3. build(), which is the one that the Builder Pattern defines to create the
AppComponent implementation instance.
Now, you can build and run and check that everything works as expected. You’ve
added a small label before the current value for the sequence, as shown in Figure 9.4:
Note: In the next chapter, you’ll learn another way to do the same thing, by
working on the @Component definition.
Adding Context this way is a common Android use case, but you could do the same
with any other object.
raywenderlich.com 223
Dagger by Tutorials Chapter 9: More About Modules
@Singleton
class SequenceViewBinderImpl @Inject constructor(
// 1
private val context: Context
) : SequenceViewBinder {
// 1
@Inject
var sequenceViewListener: SequenceViewBinder.Listener? = null
rootView.findViewById<Button>(R.id.next_value_button).setOnClick
Listener {
sequenceViewListener?.onNextValuePressed() // 2
}
}
// ...
}
Now, build and run. Hey, what’s happening? You’re getting an error like this:
You already learned in the first section of the book that it isn’t possible to inject
objects into private fields, but isn’t sequenceViewListener public by default?
raywenderlich.com 224
Dagger by Tutorials Chapter 9: More About Modules
Well, the property is, but the field is not. Dagger is a Java tool, remember? Look at
the Java code for SequenceViewBinderImpl and you’ll see something like this:
Fortunately, the Kotlin language gives you a helping hand. Just change the definition
of sequenceViewListener in SequenceViewBinderImpl.kt, like this:
@Singleton
class SequenceViewBinderImpl @Inject constructor(
private val context: Context
) : SequenceViewBinder {
@set:Inject // HERE
var sequenceViewListener: SequenceViewBinder.Listener? = null
// ...
}
By using the set: prefix, you’re telling the compiler to annotate the setter function of
the property. The Java code for this is:
@Inject // HERE
public final void setSequenceViewListener(@Nullable Listener
raywenderlich.com 225
Dagger by Tutorials Chapter 9: More About Modules
var1) {
this.sequenceViewListener = var1;
}
}
Could you use the optional type, instead? Give it a try. Open AppBindings.kt and
comment out — or simply remove — the binding for the
SequenceViewBinder.Listener type like this:
@Module
abstract class AppBindings {
// ...
/*
@Binds
abstract fun bindViewBinderListener(impl: SequencePresenter):
SequenceViewBinder.Listener
*/
}
The optional doesn’t work. The reason is still that Dagger is a Java tool, but there’s a
solution: the Optional<T> type.
Using @BindsOptionalOf
Open SequenceViewBinderImpl.kt and change the property definition like this:
@Singleton
class SequenceViewBinderImpl @Inject constructor(
private val context: Context
raywenderlich.com 226
Dagger by Tutorials Chapter 9: More About Modules
) : SequenceViewBinder {
@set:Inject
var sequenceViewListener:
Optional<SequenceViewBinder.Listener> = Optional.absent() // 1
rootView.findViewById<Button>(R.id.next_value_button).setOnClick
Listener {
// 2
if (sequenceViewListener.isPresent) {
sequenceViewListener.get().onNextValuePressed()
}
}
}
// ...
}
2. Check if the value is present using isPresent. If true, you get its reference using
get().
Build the app now, and Dagger will complain again! That’s because it has no idea
how to deal with Optional<T>. The problem is that Optional<T> is not a standard
type, like the others. And that’s exactly why @BindsOptionalOf exists.
@Module
abstract class AppBindings {
@BindsOptionalOf // HERE
abstract fun provideSequenceViewBinderListener():
SequenceViewBinder.Listener
// ...
}
raywenderlich.com 227
Dagger by Tutorials Chapter 9: More About Modules
With the previous code, you’re telling Dagger that it might find an
Optional<SequenceViewBinder.Listener> and that it shouldn’t complain if there
isn’t a binding for it.
Now you can successfully build the app — but when you press the button, nothing
happens. That’s because Optional<SequenceViewBinder.Listener>’s property
sequenceViewListener has no bindings, so it’s Optional.absent().
To make the app work again, open AppBindings.kt and restore the following
definition:
@Module
abstract class AppBindings {
// ...
@Binds
abstract fun bindViewBinderListener(impl: SequencePresenter):
SequenceViewBinder.Listener
}
Using qualifiers
RaySequence contains two different model implementations of
SequenceGenerator<T>:
• NaturalSequenceGenerator
• FibonacciSequenceGenerator
You bound each of these in AppModule.kt in different examples. For instance, you
moved from the NaturalSequenceGenerator to the FibonacciSequenceGenerator
because of the primary constructor parameter. It would be nice to have both
implementations at the same time and to make it easier to change which one you
use. However, if you just use both, Dagger will complain.
raywenderlich.com 228
Dagger by Tutorials Chapter 9: More About Modules
@Module(includes = [AppBindings::class])
interface AppModule {
@Binds
fun bindsNaturalSequenceGenerator(impl:
NaturalSequenceGenerator): SequenceGenerator<Int>
@Binds
fun bindsFibonacciSequenceGenerator(impl:
FibonacciSequenceGenerator):
SequenceGenerator<Int>
}
error: [Dagger/DuplicateBindings]
com...SequenceGenerator<java.lang.Integer> is bound multiple
times:
Of course! You get this error because you have multiple bindings for the same
abstraction. Dagger allows you to solve this problem with the concept of qualifiers.
// 1
const val NATURAL = "NaturalSequence"
const val FIBONACCI = "FibonacciSequence"
@Module(includes = [AppBindings::class])
interface AppModule {
@Binds
@Named(NATURAL) // 2
fun bindsNaturalSequenceGenerator(impl:
NaturalSequenceGenerator): SequenceGenerator<Int>
@Binds
@Named(FIBONACCI) // 3
fun bindsFibonacciSequenceGenerator(impl:
FibonacciSequenceGenerator):
SequenceGenerator<Int>
}
raywenderlich.com 229
Dagger by Tutorials Chapter 9: More About Modules
1. Define the NATURAL and FIBONACCI constants you’ll use to identify the two
different SequenceGenerator<Int> implementations.
2. Use the @Named annotation with the NATURAL constant as its parameter to
identify the NaturalSequenceGenerator implementation.
Build now, but Dagger keeps complaining. Now it doesn’t know which one to use in
SequencePresenterImpl.
@Singleton
class SequencePresenterImpl @Inject constructor(
) : BasePresenter<MainActivity,
SequenceViewBinder>(),
SequencePresenter {
@Inject
@Named(NATURAL) // HERE
lateinit var sequenceModel: SequenceGenerator<Int>
// ...
}
Using @Named, you’re telling Dagger that the instance you want to inject is the one
with the NATURAL constant as the parameter of @Named. But now that you’re
delegating creating the instance of NaturalSequenceGenerator to Dagger, it needs
some small changes. Open NaturalSequenceGenerator.kt and add @Inject to the
primary constructor, like this:
But you’re not quite finished yet. Build the app now, and you’ll get the following
error:
raywenderlich.com 230
Dagger by Tutorials Chapter 9: More About Modules
Of course! Dagger doesn’t know what to pass as the value to the constructor
parameter of type Int. But you already know how to fix this. Open AppModule.kt
and add the following definitions:
@Module(includes = [AppBindings::class])
interface AppModule {
// 2
companion object {
@Provides
@JvmStatic
@Named(START_VALUE) // 3
fun provideStartValue(): Int = 0
}
}
Now, you can finally successfully build and run and verify everything’s working as
expected.
raywenderlich.com 231
Dagger by Tutorials Chapter 9: More About Modules
Create a new package named conf, add a new file named Config.kt to it and add the
following code:
@Module(includes = [AppBindings::class])
interface AppModule {
// ...
companion object {
@Provides
@JvmStatic
fun provideConf(): Config = Config(0) // HERE
}
}
raywenderlich.com 232
Dagger by Tutorials Chapter 9: More About Modules
class NaturalSequenceGeneratorTest {
@Test
fun `test natural sequence value`() {
val naturalSequenceIterator =
NaturalSequenceGenerator(Config(0)) // HERE
listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10).forEach {
assertEquals(it, naturalSequenceIterator.next())
}
}
@Test
fun `test natural sequence value starting in diffenet value`()
{
val naturalSequenceIterator =
NaturalSequenceGenerator(Config(10)) // HERE
listOf(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20).forEach {
assertEquals(it, naturalSequenceIterator.next())
}
}
}
Create a new file named NaturalSequence.kt in the di package and add the
following code:
@Qualifier // 1
@MustBeDocumented // 2
@Retention(AnnotationRetention.BINARY) // 3
annotation class NaturalSequence //4
These few lines of code are very important — they allow you to define an annotation
named NaturalSequence.
raywenderlich.com 233
Dagger by Tutorials Chapter 9: More About Modules
2. Tag this annotation as something that’s part of a public API for a feature.
Because of this, it needs a Javadoc.
3. Set the retention of the annotation to BINARY. This means that the annotation is
stored in binary output, but invisible for reflection.
Next, create a new file in the di package named FibonacciSequence.kt and add the
following code:
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.BINARY)
annotation class FibonacciSequence
@Module(includes = [AppBindings::class])
interface AppModule {
// ...
@Binds
@NaturalSequence // 1
fun bindsNaturalSequenceGenerator(impl:
NaturalSequenceGenerator): SequenceGenerator<Int>
@Binds
@FibonacciSequence // 2
fun bindsFibonacciSequenceGenerator(impl:
FibonacciSequenceGenerator):
SequenceGenerator<Int>
}
Here, you removed the NATURAL and FIBONACCI constants and replaced @Named with:
1. @NaturalSequence
2. @FibonacciSequence
raywenderlich.com 234
Dagger by Tutorials Chapter 9: More About Modules
@Singleton
class SequencePresenterImpl @Inject constructor(
) : BasePresenter<MainActivity,
SequenceViewBinder>(),
SequencePresenter {
@Inject
@NaturalSequence // HERE
lateinit var sequenceModel: SequenceGenerator<Int>
// ...
}
With this code, you also replaced @Named with your custom @NaturalSequence.
Now, you can build and run and check that everything works.
So what do custom qualifiers cost in terms of the code Dagger generates for you?
Very little. Aside from the definitions and compilation of the source code for the
annotations themselves, Dagger doesn’t generate any additional code. It just uses
custom qualifiers to choose which specific implementation to inject. Nice job,
Dagger!
raywenderlich.com 235
Dagger by Tutorials Chapter 9: More About Modules
Since Android Studio 4.2, you can navigate the Dagger bindings directly from the
code. There are two different icons that, when you click on them, tell you where you:
In Figure 9.7, you see that the second icon allows you to find where Dagger binds
type.
raywenderlich.com 236
Dagger by Tutorials Chapter 9: More About Modules
More interesting is what happens when you click the icon in Figure 9.8 in the
@Component definition in AppComponent.kt: Android Studio shows what’s in
Figure 9.9:
raywenderlich.com 237
Dagger by Tutorials Chapter 9: More About Modules
Key points
• By using @Binds, Dagger generates fewer and shorter files, making the build more
efficient.
• Dagger @Modules allow you to provide existing objects, but you need to pass an
instance of them to the @Component builder that Dagger generates for you.
• You can provide different implementations for the same type by using @Named.
• Custom qualifiers allow you to provide different implementations for the same
type in a type-safe way.
• From Android Studio 4.1, you can easily navigate the dependency graph from the
editor.
Great job! With this chapter, you’ve completed Section Two of the book. You learned
everything you need to know about Dagger @Modules and you experimented with
using different fundamental annotations like @Binds, @Provides and
BindsOptionalOf as well as useful interfaces like dagger.Lazy<T> and
Provider<T>.
You also learned what qualifiers are, how to implement them with @Named and how
to use custom @Qualifiers.
Now, it’s time to learn everything you need about @Components. See you in the next
chapter!
raywenderlich.com 238
Section III: Components &
Scope Management
In this section, you’ll migrate the Busso App from the homemade framework to
Dagger. In the process, you’ll learn how to migrate the existing ServiceLocators and
Injectors to the equivalent Dagger @Modules and @Components, how to provide
existing objects with a customized Builder for the @Component using
@Component.Builder, and how to use @Component.Factory as a valid alternative to
@Component.Builder.
The first migration will not be optimal — there will still be some fundamental
aspects you will improve.
raywenderlich.com 239
10 Chapter 10: Understanding
Components
By Massimo Carli
In the previous chapters, you learned how to deal with @Modules in Dagger. You
learned how a @Module helps you structure the code of your app and how you can use
them to control the way Dagger creates instances of the objects in the dependency
graph.
You also had the opportunity to meet the most important concept in Dagger: the
@Component. You learned that a @Component defines the factory methods for the
objects in your app’s dependency graph. When you get a reference to an object using
a @Component’s factory method, you’re confident that Dagger has resolved all its
dependencies. You saw this in both the simple Server-Repository example and in
the more complex RaySequence app.
In this chapter, you’ll go back to working on the Busso App. You’ll learn how to:
• Provide existing objects with a customized Builder for the @Component using
@Component.Builder.
These are fundamental concepts you must understand to master Dagger. They’re also
a prerequisite for the next chapter, where you’ll learn all about scopes. So get ready
to dive in!
raywenderlich.com 240
Dagger by Tutorials Chapter 10: Understanding Components
• Allows you to implement the object you referred to as an Injector in the first
section of the book.
Don’t worry if you don’t remember everything. With Busso’s help, you’ll review all
the concepts over the course of this chapter. In particular, you’ll see how to:
To start, use Android Studio to open the Busso App from the starter folder in the
downloaded materials for this chapter. As you see in the code, the app uses the
model view presenter and has ServiceLocator and Injector implementations for
all the Activity and Fragment definitions.
In Figure 10.1, you can see the project structure of the di package, which you’ll
switch over to Dagger first.
raywenderlich.com 241
Dagger by Tutorials Chapter 10: Understanding Components
Installing Dagger
The first thing you need to do is to install the dependencies Dagger needs for the
project. Open build.gradle from the app module and apply the following changes:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt' // 1
}
apply from: '../versions.gradle'
// ...
dependencies {
// ...
// 2
// Dagger dependencies
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
}
As you learned in the previous chapters, you add Dagger support to the Busso App
by:
Select File ▸ Sync Project with Gradle Files to update the Gradle configuration in
Android Studio. You can also select the icon shown in Figure 10.2, which appears as
soon as you update some Gradle files:
raywenderlich.com 242
Dagger by Tutorials Chapter 10: Understanding Components
• @Component describes which objects in the dependency graph you can inject, along
with all their dependencies.
• @Module tells Dagger how to create the objects in the dependency graph.
With these definitions in mind, you know that you need to migrate the existing
Injectors to @Components and ServiceLocators to @Modules. Start the migration
from the SplashActivity using the dependency graph, as shown in Figure 10.3:
raywenderlich.com 243
Dagger by Tutorials Chapter 10: Understanding Components
To migrate the Busso App to Dagger, you need to describe the dependency diagram
in Figure 10.3 using Dagger annotations.
target.lookUp<ServiceLocatorFactory<AppCompatActivity>>(ACTIVITY
_LOCATOR_FACTORY)
.invoke(target)
// 3
target.splashPresenter =
activityServiceLocator.lookUp(SPLASH_PRESENTER)
target.splashViewBinder =
activityServiceLocator.lookUp(SPLASH_VIEWBINDER)
}
}
This code is familiar by now. Think about what it does and what the responsibilities
of @Components and @Modules are. You can see that:
raywenderlich.com 244
Dagger by Tutorials Chapter 10: Understanding Components
This tells you that to migrate the SplashActivityInjector class to Dagger, you
need to:
1. Define a @Module that tells Dagger how to get the SplashViewBinder and
SplashPresenter implementations.
2. Define a @Component using the previous @Module, which defines inject() for
SplashActivity.
// 1
@Module(includes = [AppModule.Bindings::class])
class AppModule {
// 2
@Module
interface Bindings {
// 3
@Binds
fun bindSplashPresenter(impl: SplashPresenterImpl):
SplashPresenter
// 4
@Binds
fun bindSplashViewBinder(impl: SplashViewBinderImpl):
SplashViewBinder
}
}
1. Create a new module named AppModule as a class. You’ll understand very soon
why it’s a class. Here, you also include the Bindings module that’s defined in the
same file. You’ve seen this pattern in previous chapters.
2. Define the Bindings interface, because you need some abstract functions that
aren’t possible in a concrete class.
raywenderlich.com 245
Dagger by Tutorials Chapter 10: Understanding Components
Dagger now knows that when you need an object of type SplashPresenter, it has to
create an instance of SplashPresenterImpl using the primary constructor, and that
constructor needs an object of type Observable<LocationEvent>. How can you tell
Dagger how to get what it needs? Simple, just add that information in the @Module.
Go back to AppModule.kt and apply the following changes to tell Dagger how to get
a reference to an object of type Observable<LocationEvent>:
@Module(includes = [AppModule.Bindings::class])
class AppModule(
// 1
private val activity: Activity
) {
// ...
// 2
@Provides
fun provideLocationObservable(): Observable<LocationEvent> {
// 3
val locationManager =
activity.getSystemService(Context.LOCATION_SERVICE) as
LocationManager
// 4
raywenderlich.com 246
Dagger by Tutorials Chapter 10: Understanding Components
val geoLocationPermissionChecker =
GeoLocationPermissionCheckerImpl(activity)
// 5
return provideRxLocationObservable(locationManager,
geoLocationPermissionChecker)
}
}
1. Add a constructor parameter of type Activity. You already learned why you
need that constructor in the last chapter, where you treated Context the same
way.
3. Use the activity you got from the AppModule primary constructor as a
parameter to get the reference to LocationManager.
Handling SplashViewBinderImpl
Open SplashViewBinderImpl.kt from ui.splash and look at the class’ header:
raywenderlich.com 247
Dagger by Tutorials Chapter 10: Understanding Components
@Module(includes = [AppModule.Bindings::class])
class AppModule(
private val activity: Activity
) {
// ...
@Provides
fun provideNavigator(): Navigator = NavigatorImpl(activity) //
HERE
}
Here, you create an instance of NavigatorImpl using the Activity you got from the
primary constructor.
Great! It’s been a long journey, but Dagger now has all the information about the
objects it needs to implement SplashActivity. It’s time to implement the
@Component and get rid of SplashInjector.
Create a new file named AppComponent.kt in the di package and add the following
content:
// 1
@Component(modules = [AppModule::class])
interface AppComponent {
// 2
fun inject(activity: SplashActivity)
}
raywenderlich.com 248
Dagger by Tutorials Chapter 10: Understanding Components
@Inject // 1
lateinit var splashViewBinder: SplashViewBinder
@Inject // 2
lateinit var splashPresenter: SplashPresenter
Note: Dagger generates DaggerAppComponent when you build the app. This
happens even if the configuration isn’t complete, unless some big mistake
prevents it. If you don’t see DaggerAppComponent, try to build the app first.
2. Do the same for the object of type SplashPresenter for the splashPresenter
property.
raywenderlich.com 249
Dagger by Tutorials Chapter 10: Understanding Components
3. Use builder() to get the reference to the Builder Dagger has created for you, for
the implementation of the AppComponent interface.
• Note that you removed the existing inject() invocation on the onCreate() in
SplashActivity.
Congrats! You completed the first step in migrating Busso to Dagger: making
SplashActivity use AppComponent to inject all its dependencies. Build and run
now, and everything works as expected:
raywenderlich.com 250
Dagger by Tutorials Chapter 10: Understanding Components
In a real app, you’d need to repeat the same process for MainActivity,
BusStopFragment and BusArrivalFragment, getting rid of all the Injector and
ServiceLocator implementations.
For this chapter, however, you have two options. You can code along and complete
the migration of Busso to Dagger, giving you some valuable practice, or you can
simply check the project in the final folder from the downloaded materials for this
chapter. In that folder, all the work has been done for you.
If you want to take the second option, jump ahead to the Customizing
@Component creation section. In that case, see you there. Otherwise, continue on.
• MainActivity
• BusStopFragment
• BusArrivalFragment
You’re about to delete a load of code. Buckle up!
Note: It’s important to note that the following migration is not the best. You
still need to manage different @Components for different @Scopes, which isn’t
ideal. Don’t worry, you’ll fix this in the next chapter.
Migrating MainActivity
Open MainActivityInjector.kt from di.injectors and look at the following code:
target.lookUp<ServiceLocatorFactory<AppCompatActivity>>(ACTIVITY
_LOCATOR_FACTORY)
.invoke(target)
raywenderlich.com 251
Dagger by Tutorials Chapter 10: Understanding Components
target.mainPresenter =
activityServiceLocator.lookUp(MAIN_PRESENTER) // HERE
}
}
This code tells you that MainActivity depends on the MainPresenter abstraction.
This is just one thing you can see from the complete dependency graph in Figure
10.5:
raywenderlich.com 252
Dagger by Tutorials Chapter 10: Understanding Components
Note that Dagger knows some of this information already, like how to bind
NavigatorImpl to Navigator. What Dagger doesn’t know is how to bind
MainPresenterImpl to MainPresenter.
Open AppModule.kt and add the following definition to the Bindings interface:
@Module(includes = [AppModule.Bindings::class])
class AppModule(
private val activity: Activity
) {
@Module
interface Bindings {
// ...
@Binds
fun bindMainPresenter(impl: MainPresenterImpl):
MainPresenter // HERE
}
// ...
}
Here, you use @Binds to bind MainPresenterImpl to MainPresenter. Now, you need
to tell Dagger how to create the instance of MainPresenterImpl. Open
MainPresenterImpl.kt from ui.view.main and apply the following, now obvious,
change:
You use @Inject to tell Dagger that it needs to invoke the primary constructor to
create an instance of MainPresenterImpl. It already knows how to provide a
Navigator implementation.
Now, you need to define inject() for the MainActivity in the AppComponent. Open
AppComponent.kt from di and add the following definition:
@Component(modules = [AppModule::class])
interface AppComponent {
// ...
fun inject(activity: MainActivity) // HERE
}
raywenderlich.com 253
Dagger by Tutorials Chapter 10: Understanding Components
It’s very important to note that the parameter type matters. The parameter must
match the target’s type for the injections. In short, you can’t use a parameter of type
Activity, which would include both MainActivity and SplashActivity.
Remember that you’re giving Dagger information, you’re not writing actual code;
Dagger creates the code for you. Again, inject()’s name doesn’t matter, it’s just a
convention.
For your last step, open MainActivity.kt from ui.view.main and apply the following
changes:
@Inject // 1
lateinit var mainPresenter: MainPresenter
1. Use @Inject to tell Dagger you want the reference to the MainPresenter
implementation in the mainPresenter property.
Now, build and run and check that everything works. Then, delete
MainActivityInjector.kt, since you don’t it need anymore.
raywenderlich.com 254
Dagger by Tutorials Chapter 10: Understanding Components
• BussoEndpoint
• Observable<LocationEvent>
These are objects you manage at the Activity level. At this point, you need to:
raywenderlich.com 255
Dagger by Tutorials Chapter 10: Understanding Components
1. Add the comp property of type AppComponent. This is the reference to the
AppComponent instance you create for MainActivity.
3. Define comp as an extension property for the Context type. If the Context
receiver IS-A MainActivity, it’s the reference to the AppComponent instance.
Otherwise, it’s null.
Now, you just need to tell Dagger how to create an implementation for the
BussoEndpoint type and then migrate the two Fragments.
@Module
class NetworkModule(val context: Context) { // HERE
@Provides
fun provideBussoEndPoint(): BussoEndpoint {
val cache = Cache(context.cacheDir, CACHE_SIZE)
val okHttpClient = OkHttpClient.Builder()
.cache(cache)
.build()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BUSSO_SERVER_BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create(
))
.addConverterFactory(
GsonConverterFactory.create(
GsonBuilder().setDateFormat("yyyy-MM-
dd'T'HH:mm:ssZ").create()
)
)
.client(okHttpClient)
.build()
return retrofit.create(BussoEndpoint::class.java)
raywenderlich.com 256
Dagger by Tutorials Chapter 10: Understanding Components
}
}
This code is very similar to the one in BussoEndpoint.kt in the same package. In
that case, the signature has Context as a parameter.
@Module
class NetworkModule(val context: Context) { // HERE
@Provides
fun provideBussoEndPoint(): BussoEndpoint {
// ...
}
}
@Component(modules = [AppModule::class,
NetworkModule::class]) // HERE
interface AppComponent {
// ...
}
This last change has some consequences. You’re telling Dagger that the
AppComponent implementation needs a NetworkModule. This means that Dagger will
generate a function in the builder for it.
raywenderlich.com 257
Dagger by Tutorials Chapter 10: Understanding Components
.appModule(AppModule(this))
.networkModule(NetworkModule(this)) // HERE
.build().apply {
inject(this@MainActivity)
}
if (savedInstanceState == null) {
mainPresenter.goToBusStopList()
}
}
}
// ...
Note: Yeah. There’s a lot of repetition here. Don’t worry, you’ll get rid of all of
this very soon.
raywenderlich.com 258
Dagger by Tutorials Chapter 10: Understanding Components
Migrating BusStopFragment
Your next step is to quickly migrate the BusStopFragment following the same
process you used above. Open AppModule.kt and add the following bindings:
@Module(includes = [AppModule.Bindings::class])
class AppModule(
private val activity: Activity
) {
@Module
interface Bindings {
// ...
@Binds
fun bindBusStopListViewBinder(impl:
BusStopListViewBinderImpl): BusStopListViewBinder
@Binds
fun bindBusStopListPresenter(impl:
BusStopListPresenterImpl): BusStopListPresenter
raywenderlich.com 259
Dagger by Tutorials Chapter 10: Understanding Components
@Binds
fun bindBusStopListViewBinderListener(impl:
BusStopListPresenterImpl):
BusStopListViewBinder.BusStopItemSelectedListener
}
// ...
}
Note: You had the same problem with the RaySequence app, but you solved it
using @Singleton.
@Singleton // 1
class BusStopListPresenterImpl @Inject constructor( // 2
private val navigator: Navigator,
private val locationObservable: Observable<LocationEvent>,
private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusStopListViewBinder>(),
BusStopListPresenter {
// ...
}
Note that Dagger knows all about the primary constructor parameter types.
Remember to use @Singleton to ensure you’re using the same instance of
BusStopListPresenterImpl for BusStopListPresenter and for
BusStopListViewBinder.BusStopItemSelectedListener.
raywenderlich.com 260
Dagger by Tutorials Chapter 10: Understanding Components
Here, you’re:
@Inject // 1
lateinit var busStopListViewBinder: BusStopListViewBinder
@Inject // 1
lateinit var busStopListPresenter: BusStopListPresenter
raywenderlich.com 261
Dagger by Tutorials Chapter 10: Understanding Components
Migrating BusArrivalFragment
You’re very close to completing the migration of Busso to Dagger. Open
AppModule.kt and add the following bindings:
@Module(includes = [AppModule.Bindings::class])
class AppModule(
private val activity: Activity
) {
@Module
interface Bindings {
// ...
@Binds
fun bindBusArrivalPresenter(impl: BusArrivalPresenterImpl):
BusArrivalPresenter
@Binds
fun bindBusArrivalViewBinder(impl:
BusArrivalViewBinderImpl): BusArrivalViewBinder
}
// ...
}
raywenderlich.com 262
Dagger by Tutorials Chapter 10: Understanding Components
@Inject // 1
lateinit var busArrivalPresenter: BusArrivalPresenter
Here, you follow the same approach you used for BusStopFragment. Build the
project now. You’ll see some compilation errors, but only because you need some
clean-up. :]
• injectors package
• locators package
• Main.kt file in the app’s main package and its reference in AndroidManifest.xml
raywenderlich.com 263
Dagger by Tutorials Chapter 10: Understanding Components
After this, the directory structure for the project will match Figure 10.7.
raywenderlich.com 264
Dagger by Tutorials Chapter 10: Understanding Components
@Module(includes = [AppModule.Bindings::class])
class AppModule(
private val activity: Activity // HERE
) {
// ...
}
This method lets you use the existing object — activity, in this case — in any
@Provides function in the same AppModule.kt. That’s because activity acts like a
normal property of AppModule.
Then, you need to explicitly create the @Module instance and use it during the
building process for the @Component, as you did in SplashActivity.kt:
Here, you also had to create the NetworkModule for the same purpose: to provide an
implementation of Context. It would be nice if you could pass the reference to an
existing object to @Component by adding it directly to the dependency graph. That
would make the provided object accessible to any other objects that depend on it.
Fortunately, you can actually do that by using @Component.Builder and
@Component.Factory.
raywenderlich.com 265
Dagger by Tutorials Chapter 10: Understanding Components
Using @Component.Builder
Open AppComponent.kt and add the following code:
@BindsInstance // 2
fun activity(activity: Activity): Builder
These few lines of code are very important. In the AppComponent interface, you:
2. Define a function that accepts a unique parameter of the type you want to
provide. In this case, you define activity() with a parameter of type Activity.
This function must return the same Builder because it’s going to generate one
of the methods you must invoke to pass the existing object to the @Component.
To make that object available to the dependency graph, you must annotate the
function with @BindsInstance. In short, here you tell Dagger that your
AppComponent needs an Activity and that you’re providing its reference by
invoking activity() on the Builder implementation Dagger generates for you.
If you don’t use the @BindsInstance annotation, you’ll get the not-so-clear
message: error: @Component.Builder has setters for modules or
components that aren’t required.
Build the app now and you’ll get some compilation errors. This happens because you
told Dagger that you want to provide a custom Builder for the AppComponent
implementation that ignores AppModule and NetworkModule.
raywenderlich.com 266
Dagger by Tutorials Chapter 10: Understanding Components
One solution is to implement the exact same Builder Dagger would. Just replace the
previous Builder implementation with the following:
Now, you can successfully build and run because the custom Builder
implementation you configured is exactly the same as the one Dagger would have
created without the @Component.Builder. The appModule() and networkModule
functions have the same name.
Note: It’s important to know that it’s not mutually exclusive to pass the
reference to both a @Module and an existing object. You could have both in
your Builder. Also note that, in this case, you don’t need to use
@BindsInstance.
Of course this isn’t what you want. Restore the previous version of Builder in
AppComponent.kt and apply the following changes to AppModule.kt:
@Module(includes = [AppModule.Bindings::class])
class AppModule { // 1
// ...
@Provides // 2
fun provideNavigator(activity: Activity): Navigator =
NavigatorImpl(activity)
@Provides // 3
fun provideLocationObservable(activity: Activity):
Observable<LocationEvent> {
val locationManager =
activity.getSystemService(Context.LOCATION_SERVICE) as
LocationManager
val geoLocationPermissionChecker =
GeoLocationPermissionCheckerImpl(activity)
return provideRxLocationObservable(locationManager,
raywenderlich.com 267
Dagger by Tutorials Chapter 10: Understanding Components
geoLocationPermissionChecker)
}
}
You basically treat the Activity type as something that’s already part of the
dependency graph for the @Component.
Now, open NetworkModule.kt and make the same change, like this:
@Module
class NetworkModule { // 1
@Provides // 2
fun provideBussoEndPoint(activity: Activity): BussoEndpoint {
val cache = Cache(activity.cacheDir, CACHE_SIZE)
// ...
}
}
Build the app now, and you’ll still get some compilation errors because the Builder
implementation for the AppComponent has changed. To fix this, open
MainActivity.kt and apply the following changes:
raywenderlich.com 268
Dagger by Tutorials Chapter 10: Understanding Components
}
}
}
// ...
In the code above, you simply use activity() to pass the reference to the existing
object to the dependency graph. Because of this, NetworkModule also gets the
reference to the same object.
Before you build and run, you also need to apply the same change to
SplashActivity.kt, like this:
Now you can finally build and run the app successfully.
Using @Component.Factory
Using @Component.Builder, you learned how to customize the Builder
implementation for the @Component Dagger creates for you. Usually, you get the
reference to the Builder implementation, then you invoke some setter methods
that pass the parameter you need and finally, you invoke build() to create the final
object.
raywenderlich.com 269
Dagger by Tutorials Chapter 10: Understanding Components
// ...
@Component.Factory // 1
interface Factory {
// 2
fun create(@BindsInstance activity: Activity): AppComponent
}
}
2. A create() function with a parameter of type Activity. create() can have any
parameters you need, but it must return an object with the same type as the
@Component. In this case, the return type is AppComponent. @BindsInstance here
has the same meaning you learned in the previous paragraph: If you use it for a
parameter, the provided value becomes part of the dependency graph and is
available for injection.
Build and run the project and you’ll get some compilation errors. That’s because
Dagger generates different code now. To fix this, open SplashActivity.kt and apply
the following changes:
raywenderlich.com 270
Dagger by Tutorials Chapter 10: Understanding Components
Of course, you need to do the same for MainActivity as well, applying the same
changes:
In both cases, you can pass one of these objects as optional by using @Nullable as its
parameter. @Component.Factory allows you to write less code, passing all the
parameters in one call, while @Component.Builder is a little bit more verbose.
Great job! In this chapter, you finally migrated the Busso App from your homemade
framework to Dagger. This long, and occasionally repetitive process reduced the
number of lines of code in your project. Well, at least the lines of code you wrote.
The Dagger configuration you used in this chapter is still not optimal, however. To
use your resources more efficiently, some components should have different
lifecycles than others. While the BussoEndpoint should live as long as the app, the
Navigator lifecycle should be bound to the Activity lifecycle.
In the next chapter, you’ll make more important changes. See you there!
raywenderlich.com 271
Dagger by Tutorials Chapter 10: Understanding Components
Key points
• The most important concept to understand in Dagger is the @Component, which
works as the factory for the objects in the dependency graph.
raywenderlich.com 272
11 Chapter 11: Components
& Scopes
By Massimo Carli
In the previous chapter, you migrated the Busso App from a homemade framework
with ServiceLocators and Injectors to Dagger. You converted Injectors into
@Components and ServiceLocators into @Modules, according to their
responsibilities. You learned how to manage existing objects with types like Context
or Activity by using a @Component.Builder or @Component.Factory.
However, the migration from the previous chapter isn’t optimal — there are still
some fundamental aspects you need to improve. For instance, in the current code,
you:
raywenderlich.com 273
Dagger by Tutorials Chapter 11: Components & Scopes
These are just some of the problems you’ll fix in this chapter. You’ll also learn:
• What a lifecycle is, why it’s important and what its relationship to scope is.
It’s going to be a very interesting and important chapter, so get ready to dive in!
Note: The getter and setter thing is probably a consequence of the JavaBean
specification that Sun Microsystems released way back in 1997. A JavaBean is a
Java component that’s reusable and that a visual IDE can edit. The last
property is the important one. An IDE that wants to edit a JavaBean needs to
know what the component’s properties, events and methods are. In other
words, the component needs to describe itself to the container, either
explicitly — by using BeanInfo — or implicitly. To use the implicit method, you
need to follow some conventions, one of which is that a component has the
property prop of type T if it has two methods — getProp(): T and
setProp(T). Because of this, a JavaBean can have getters and setters — but
even when a class has getters and setters, it’s not necessarily a JavaBean.
raywenderlich.com 274
Dagger by Tutorials Chapter 11: Components & Scopes
A related interview question in Android is, “What are the standard components of
the Android platform?” The correct answer is:
• Activity
• Service
• ContentProvider
• BroadcastReceiver
In this case, the following applies to the components:
1. The Android environment is the container that manages the lifecycle of standard
components according to the available system resources and user actions.
3. When you define an Android component, you provide implementations for some
callback functions, including onCreate(), onStart(), onStop() and so on. The
container invokes these to send a notification that there’s a transition to a
different state in the lifecycle.
raywenderlich.com 275
Dagger by Tutorials Chapter 11: Components & Scopes
But why, then, do you need to delegate the lifecycle of those components to the
container — in this case, the Android environment? Because it’s the system’s
responsibility to know which resources are available and to decide what can live and
what should die. This is true for all the Android standard components and
Fragments. But all these components have dependencies.
In the Busso App, you’ve seen how an Activity or Fragment depends on other
objects, which have a presenter, model or viewBinder role. If a component has a
lifecycle, its dependencies do, too. Some objects need to live as long as the app and
others only need to exist when a specific Fragment or Activity is visible.
As you can see, you still have work to do to improve the Busso App.
@Singleton
@Component(modules = [AppModule::class, NetworkModule::class])
interface AppComponent {
raywenderlich.com 276
Dagger by Tutorials Chapter 11: Components & Scopes
@Component.Factory
interface Factory {
The AppComponent interface is responsible for the entire app’s dependency graph.
You have inject() overloads for all the Fragment and Activity components and
you’re using all the @Modules. That means all the app’s objects are part of the same
graph. This has important implications for scope.
• By default, Dagger creates a new instance of the class that’s bound to a specific
abstraction type every time an injection of that type occurs.
• Using @Singleton, you can only bind the lifecycle of an instance to the lifecycle of
the @Component you use to get its reference.
Using @Singleton doesn’t give you a unique instance of a class across the entire app.
It just means that each instance of a @Component always returns the same instance
for an object of a given type. Different @Component instances will return different
instances for the same type, even if you use @Singleton.
As you’ll see later, if you want an instance of an object that lives as long as the app
does, you need a @Component that matches the app’s lifespan.
To better understand this concept, your next step will be to fix the BussoEndpoint in
the Busso app.
raywenderlich.com 277
Dagger by Tutorials Chapter 11: Components & Scopes
Fixing BussoEndpoint
The BussoEndpoint implementation is a good place to start optimizing the Busso
App. It’s a typical example of a component that needs to be unique across the entire
app. To recap, BussoEndpoint is an interface which abstracts the endpoint for the
application.
• BusStopListPresenterImpl
• BusArrivalPresenterImpl
Open BusStopListPresenterImpl.kt in ui.view.busstop and add the following init
block:
@Singleton
class BusStopListPresenterImpl @Inject constructor(
private val navigator: Navigator,
private val locationObservable: Observable<LocationEvent>,
private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusStopListViewBinder>(),
BusStopListPresenter {
// HERE
init {
Log.d("BUSSOENDPOINT", "StopList: $bussoEndpoint")
}
// ...
}
raywenderlich.com 278
Dagger by Tutorials Chapter 11: Components & Scopes
That just added some logs that print information about the specific BussoEndpoint
instance. Build and run, then navigate back and forth a few times in the arrivals
fragment, and you’ll get a log like this:
Using @Singleton
As you’ve learned, using @Singleton is the first solution to the multiple instances
problem. In this specific case, however, something’s different: You can’t access the
code of the class that’s bound to the BussoEndpoint interface because the Retrofit
framework created it for you.
However, using Dagger gives you a way to get around this problem. Open
NetworkModule.kt in the network and apply the following change:
@Module
class NetworkModule {
@Provides
@Singleton // HERE
fun provideBussoEndPoint(activity: Activity): BussoEndpoint {
raywenderlich.com 279
Dagger by Tutorials Chapter 11: Components & Scopes
// ...
}
}
Now, the instance for the BussoEndpoint type is always the same. Everything seems
to work, but this isn’t a good solution for the reason mentioned above. Using
@Singleton didn’t give you a single instance for the BussoEndpoint
implementation for the entire app.
With this configuration, you’re creating an instance for BussoEndpoint for each
instance of AppComponent — but you have one AppComponent per Activity. In the
app, you have two of them.
To create a single BussoEndpoint instance across the entire app, you need to define
a @Component with the same lifespan. This is called an application scope. You’ll
learn how to define and use application scopes next.
You already know what to do to make Dagger provide a single instance for a given
type across the entire app. You need to define a:
1. @Module that encapsulates the information about how to create the objects.
3. Way to make the @Component live as long the app, and make it accessible from
any other point in the code.
raywenderlich.com 280
Dagger by Tutorials Chapter 11: Components & Scopes
Look at Busso’s code and you’ll see that there are three types of objects that have an
application scope:
• BussoEndpoint
• LocationManager
• GeoLocationPermissionChecker
You’ll start by defining the @Module.
Defining ApplicationModule
This @Module tells Dagger how to create the objects it needs for the application
scope. You have an opportunity to improve the structure of your code by putting
your new Dagger knowledge to work.
You don’t want to have all the definitions in a single place, so your first step is to
improve some of the existing files. Open NetworkModule.kt in the network and
add the following:
@Module
class NetworkModule {
// 1
@Provides
@Singleton
fun provideCache(application: Application): Cache =
Cache(application.cacheDir, 100 * 1024L)// 100K
// 2
@Provides
@Singleton
raywenderlich.com 281
Dagger by Tutorials Chapter 11: Components & Scopes
This code gives you a better definition of the dependencies. In particular, you define:
raywenderlich.com 282
Dagger by Tutorials Chapter 11: Components & Scopes
Now, create a new file named LocationModule.kt in di and add the following code:
@Module
class LocationModule {
// 1
@Singleton
@Provides
fun provideLocationManager(application: Application):
LocationManager =
application.getSystemService(Context.LOCATION_SERVICE) as
LocationManager
// 2
@Singleton
@Provides
fun providePermissionChecker(application: Application):
GeoLocationPermissionChecker =
GeoLocationPermissionCheckerImpl(application)
// 3
@Provides
fun provideLocationObservable(
locationManager: LocationManager,
permissionChecker: GeoLocationPermissionChecker
): Observable<LocationEvent> =
provideRxLocationObservable(locationManager, permissionChecker)
}
This refactoring is similar to the one you did before, but it’s a bit more important. In
this code, you define:
Note that in LocationManager’s case, you don’t actually know if you need
@Singleton or not. That’s because the LocationManager you get from the Context
using getSystemService() might already be a Singleton in the Design Pattern
sense.
raywenderlich.com 283
Dagger by Tutorials Chapter 11: Components & Scopes
Finally, you’ll merge the two modules into one as a convenience. Create a new file
named ApplicationModule.kt in di and enter the following code:
@Module(includes = [
LocationModule::class,
NetworkModule::class
])
object ApplicationModule
This lets you keep all the @Modules for the application scope objects in the same
place.
Defining ApplicationComponent
Now, you need to define the @Component for the objects with application scope. The
main thing to note here is that NetworkModule and LocationModule need an
Application, which is an object you don’t have to create. You provide it instead.
@Component(modules = [ApplicationModule::class]) // 1
@Singleton // 2
interface ApplicationComponent {
@Component.Factory
interface Builder {
2. Use @Singleton because you have bindings that should have the same lifespan
as the ApplicationComponent.
raywenderlich.com 284
Dagger by Tutorials Chapter 11: Components & Scopes
You don’t have any inject() functions in this code because you won’t use the
ApplicationComponent directly. As you’ll see shortly, you just need a way to make
these objects available to other @Components with different @Scopes.
Note: You’ll learn all about @Component dependency in the next chapter.
Here, you’ll use the dependencies attribute of the @Component annotation,
which is also the approach originally implemented in Dagger.
To do that, Dagger asks you to provide factory methods for the objects you want to
expose. To access Observable<LocationEvent> and BussoEndpoint directly from
the ApplicationComponent, you need to add the following code to the @Component
definition in ApplicationComponent.kt.
@Component(modules = [ApplicationModule::class])
@Singleton
interface ApplicationComponent {
// 1
fun locationObservable(): Observable<LocationEvent>
// 2
fun bussoEndpoint(): BussoEndpoint
@Component.Factory
interface Builder {
raywenderlich.com 285
Dagger by Tutorials Chapter 11: Components & Scopes
Once you get the reference to ApplicationComponent’s instance, you just need to
invoke:
Start by creating a new file named Main.kt in the main package of the app with the
following code:
// 1
class Main : Application() {
// 2
lateinit var appComponent: ApplicationComponent
raywenderlich.com 286
Dagger by Tutorials Chapter 11: Components & Scopes
Here, you:
3. Invoke create() on the Factory you get from the static factory method,
factory(). This passes the reference to the Application itself and saves the
ApplicationComponent instance in the appComponent property.
4. Define the appComp extension property for the Context type. If you try to access
this property from a Context that’s not using Main as its ApplicationContext,
you’ll get a crash. This is good because it lets you catch errors early in
development.
Now, open AndroidManifest.xml and apply the following change, adding the
android:name attribute for the application element:
<application ...
android:name=".Main"> // HERE
// ...
</application>
</manifest>
You’ve now set up everything you need to manage objects with application scope.
But how can you actually use them?
Check out SplashActivity and you see that it needs the reference to the
SplashPresenter and SplashViewBinder implementations. SplashPresenterImpl
needs the reference to Observable<LocationEvent>, but SplashViewBinderImpl
needs a Navigator that depends on the Activity — and that’s not available at the
Application level. That means you need another scope.
raywenderlich.com 287
Dagger by Tutorials Chapter 11: Components & Scopes
You also realized that there are other objects, like Navigator, that need an
Activity, instead. You need to bind the lifecycle of these objects to the lifecycle of
the Activity they depend upon. Based on what you’ve learned so far, you should
just follow these steps:
1. Create the @Modules for the objects that depend on the Activity.
3. Use @Singleton for the objects you want to bind to the Activity lifecycle.
This is exactly what you’re going to do, but with a small but very important
difference: You’re going to create and use a new @Scope named @ActivityScope.
Creating @ActivityScope
As you read in the previous chapters, @Singleton is nothing special. It doesn’t say
that an object is a Singleton, it just tells Dagger to bind the lifecycle of the object to
the lifestyle of a @Component. For this reason, it’s a good practice to define a custom
scope using the @Scope annotation.
To do this, create a new file named ActivityScope.kt in a new di.scopes package and
add the following code:
@Scope // 1
@MustBeDocumented // 2
@Retention(RUNTIME) // 3
annotation class ActivityScope // 4
raywenderlich.com 288
Dagger by Tutorials Chapter 11: Components & Scopes
You already defined a custom annotation when you learned about custom qualifiers.
This isn’t much different. Here, you:
1. Use @Scope to mark this annotation as a way to define a scope. Like @Singleton,
@Scope is part of Java Specification Request 330. In @Singleton’s source code,
you can see it has the same @Scope annotation.
2. Use @MustBeDocumented to mark that a Java doc should be generated for this
annotation.
3. Apply RUNTIME as a retention value. This means that you store the annotation in
binary output where it’s visible for reflection.
Now, you’re ready to use the @ActivityScope in the same way you used
@Singleton. The only difference is the name.
• Navigator
• SplashPresenter
• SplashViewBinder
• MainPresenter
To do this, create a new file named ActivityModule.kt in di and enter the following
code:
@Module(includes = [ActivityModule.Bindings::class])
class ActivityModule {
@Module
interface Bindings {
@Binds
fun bindSplashPresenter(impl: SplashPresenterImpl):
SplashPresenter
@Binds
fun bindSplashViewBinder(impl: SplashViewBinderImpl):
SplashViewBinder
@Binds
fun bindMainPresenter(impl: MainPresenterImpl):
MainPresenter
raywenderlich.com 289
Dagger by Tutorials Chapter 11: Components & Scopes
@Provides
@ActivityScope
fun provideNavigator(activity: Activity): Navigator =
NavigatorImpl(activity)
}
It’s important to note that you use @ActivityScope to tell Dagger it should always
return the same instance of the Navigator implementation if you get the reference
through a @Component that has @ActivityScope as one of the scopes it supports. At
the moment, you don’t have any @Components like that — so it’s time to create one.
Remember to delete the same definitions from the existing AppModule.kt file.
@Component(
modules = [ActivityModule::class] // 1
)
@ActivityScope // 2
interface ActivityComponent {
@Component.Factory
interface Factory {
// 5
fun create(@BindsInstance activity: Activity):
ActivityComponent
}
}
2. Use @ActivityScope to tell Dagger that the objects that use the same annotation
have a lifecycle bound to an ActivityComponent.
raywenderlich.com 290
Dagger by Tutorials Chapter 11: Components & Scopes
4. Define navigator() as the factory method for Navigator. This is also necessary
to make Navigator visible to its dependent @Component.
1. Tell Dagger that you need the objects from another @Component.
raywenderlich.com 291
Dagger by Tutorials Chapter 11: Components & Scopes
Open ActivityComponent.kt, which you just created in di, and make the following
changes:
@Component(
modules = [ActivityModule::class],
dependencies = [ApplicationComponent::class] // 1
)
@ActivityScope
interface ActivityComponent {
@Component.Factory
interface Factory {
fun create(
@BindsInstance activity: Activity,
applicationComponent: ApplicationComponent // 2
): ActivityComponent
}
}
This is going to change the code Dagger generates for you. To see how, you’ll use this
component in SplashActivity and MainActivity.
raywenderlich.com 292
Dagger by Tutorials Chapter 11: Components & Scopes
makeFullScreen()
setContentView(R.layout.activity_splash)
DaggerActivityComponent.factory() // 1
.create(this, this.application.appComp) // 2
.inject(this) // 3
splashViewBinder.init(this)
}
// ...
}
1. Use factory() to get the Factory implementation that Dagger created for you.
2. Invoke create(), passing the reference to the current Activity as the first
parameter. As the second parameter, you pass the reference to the
ApplicationComponent that you get by using the appComp extended property
you defined in Main.kt. It’s fundamental to understand that you only have one
instance of appComp across the entire app.
Now, you can do the same for MainActivity. Open MainActivity.kt in the
ui.view.main and apply the following changes:
@Inject
lateinit var mainPresenter: MainPresenter
raywenderlich.com 293
Dagger by Tutorials Chapter 11: Components & Scopes
What about the Fragments? Well, at this point, you should know exactly what to do
— create another custom scope.
Create a new file named FragmentScope.kt in di.scopes with the following code:
@Scope
@MustBeDocumented
@Retention(RUNTIME)
annotation class FragmentScope
This defines the new @FragmentScope. The only way this code differs from
@Singleton and @ActivityScope is in its name.
Now, create a new file named FragmentModule.kt in the di folder and add the
following code:
@Module
interface FragmentModule {
@Binds
fun bindBusStopListViewBinder(impl:
BusStopListViewBinderImpl): BusStopListViewBinder
@Binds
fun bindBusStopListPresenter(impl: BusStopListPresenterImpl):
BusStopListPresenter
raywenderlich.com 294
Dagger by Tutorials Chapter 11: Components & Scopes
@Binds
fun bindBusStopListViewBinderListener(impl:
BusStopListPresenterImpl):
BusStopListViewBinder.BusStopItemSelectedListener
@Binds
fun bindBusArrivalPresenter(impl: BusArrivalPresenterImpl):
BusArrivalPresenter
@Binds
fun bindBusArrivalViewBinder(impl: BusArrivalViewBinderImpl):
BusArrivalViewBinder
}
This is nothing new — you just define the bindings for the components you need in
the Fragments of the Busso App. Note that this code contains the remaining
definitions you had in AppModule.kt, so delete that file now.
@Component(
modules = [FragmentModule::class],
dependencies = [ActivityComponent::class,
ApplicationComponent::class] // 1
)
@FragmentScope // 2
interface FragmentComponent {
@Component.Factory
interface Factory {
// 4
fun create(
applicationComponent: ApplicationComponent,
activityComponent: ActivityComponent
): FragmentComponent
}
}
raywenderlich.com 295
Dagger by Tutorials Chapter 11: Components & Scopes
3. Definition of the inject() methods for the actual injection of the dependencies
in BusStopFragment and BusArrivalFragment.
@FragmentScope // HERE
class BusStopListPresenterImpl @Inject constructor(
private val navigator: Navigator,
private val locationObservable: Observable<LocationEvent>,
private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusStopListViewBinder>(),
BusStopListPresenter {
// ...
}
raywenderlich.com 296
Dagger by Tutorials Chapter 11: Components & Scopes
.inject(this@BusStopFragment)
}
super.onAttach(context)
}
// ...
}
The only thing to mention here is that you use the references to
ApplicationComponent and ActivityComponent as parameters of the factory
method for the FragmentComponent.
Now, you can finally build and run Busso and check that everything works as
expected.
raywenderlich.com 297
Dagger by Tutorials Chapter 11: Components & Scopes
Key points
• There’s no component without a container that’s responsible for its lifecycle.
• @Scopes let you bind the lifecycle of an object to the lifecycle of a @Component.
Great job! This was another important chapter, and you managed to implement
different @Scopes for Busso’s components. But you’re far from done! Dagger is
evolving and there are many other important concepts to learn. See you in the next
chapter.
raywenderlich.com 298
12 Chapter 12: Components
Dependencies
By Massimo Carli
In the previous chapter, you achieved a very important goal: You optimized the
Busso App by defining different @Components with different @Scopes. Using the
existing @Singleton as well as custom @ActivityScopes and @FragmentScopes, you
gave each object the right lifecycle, optimizing the app’s resources. In the process,
you had the opportunity to experience what component dependency means.
In this chapter, you’ll learn even more about @Components and dependencies. In
particular, you’ll learn:
raywenderlich.com 299
Dagger by Tutorials Chapter 12: Components Dependencies
To prove that, and to be more consistent with names, create a new @Scope named
@ApplicationScope and see what happens when you use it instead of @Singleton.
Create a new file named ApplicationScope.kt in the di.scopes package and add the
following code:
@Scope
@MustBeDocumented
@Retention(RUNTIME)
annotation class ApplicationScope
@Component(modules = [ApplicationModule::class])
@ApplicationScope // HERE
interface ApplicationComponent {
// ...
}
@Module
class LocationModule {
@ApplicationScope // HERE
@Provides
fun provideLocationManager(application: Application):
LocationManager =
application.getSystemService(Context.LOCATION_SERVICE) as
LocationManager
@ApplicationScope // HERE
@Provides
fun providePermissionChecker(application: Application):
GeoLocationPermissionChecker =
GeoLocationPermissionCheckerImpl(application)
// ...
}
raywenderlich.com 300
Dagger by Tutorials Chapter 12: Components Dependencies
@Module
class NetworkModule {
@Provides
@ApplicationScope // HERE
fun provideCache(application: Application): Cache =
Cache(application.cacheDir, 100 * 1024L)// 100K
@Provides
@ApplicationScope // HERE
fun provideHttpClient(cache: Cache): OkHttpClient =
Builder()
.cache(cache)
.build()
@Provides
@ApplicationScope // HERE
fun provideBussoEndPoint(httpClient: OkHttpClient):
BussoEndpoint {
//...
}
}
Now, you can build and run Busso to prove that nothing’s changed, other than using
more consistent naming in the app’s @Scopes.
raywenderlich.com 301
Dagger by Tutorials Chapter 12: Components Dependencies
Component dependencies
Using the dependencies attribute of @Component lets you add objects from one
dependency graph to the objects of another. It’s a kind of graph composition. If
@Component A needs some objects that are part of the dependency graph of
@Component B, you just add B as one of the dependencies for A.
raywenderlich.com 302
Dagger by Tutorials Chapter 12: Components Dependencies
Something that allows you to say that @Component A IS-A @Component B, and so
inherits all the objects in its dependency graph, as shown in Figure 12.2.
raywenderlich.com 303
Dagger by Tutorials Chapter 12: Components Dependencies
• ApplicationComponent
• ActivityComponent
• FragmentComponent
for three different @Scopes:
• @ApplicationScope
• @ActivityScope
• @FragmentScope
That was quite straightforward. The most interesting part is the way objects from
different components depend on each other. Objects with @ApplicationScope
should be available to objects with @ActivityScope and @FragmentScope, while
objects with @ActivityScope should be also accessible from objects with
@FragmentScope.
You can represent the relationship between objects with @ApplicationScope and
@ActivityScope, as shown in Figure 12.3:
raywenderlich.com 304
Dagger by Tutorials Chapter 12: Components Dependencies
• ActivityComponent
• ApplicationComponent
It also tells you many interesting things:
1. The «Provided» stereotype tells you which objects you need to explicitly
provide when you create an instance of a @Component. For instance, you need to
provide an Application when you create an @ApplicationComponent and an
Activity when you create an ActivityComponent.
2. When you create the ActivityComponent instance, you have to provide the
instance of the ApplicationComponent it depends on. That’s why
ApplicationComponent has the Provided stereotype. The same is true for the
ActivityComponent that you need to provide to the FragmentComponent —
although, to keep the diagram clean and clear, that isn’t shown above.
3. Objects with a gray background are private to the @Component. This means that
these objects aren’t directly visible to other @Components using the
dependencies attribute. This is important because you can only access objects
you explicitly expose using factory methods, as you did in
ApplicationComponent.kt:
@Component(modules = [ApplicationModule::class])
@ApplicationScope
interface ApplicationComponent {
@Component.Factory
interface Builder {
raywenderlich.com 305
Dagger by Tutorials Chapter 12: Components Dependencies
If you consider only the provided and public objects, Figure 12.4 shows what
happens for the FragmentComponent:
This is all well and good, but Dagger allows you to implement the same thing using
@Subcomponents, creating a sort of inheritance relationship among dependency
graphs with different @Scopes.
raywenderlich.com 306
Dagger by Tutorials Chapter 12: Components Dependencies
Using @Subcomponents
So, you’ve just learned in detail how the objects of different @Components of the
Busso App depend on one other. You also learned that Busso requires some kind of
inheritance relationship between the @Components because you want to use the
objects from the ApplicationComponent in the ActivityComponent and in the
FragmentComponent. You also want to use the objects of the ActivityComponent in
the FragmentComponent.
To do all this, you need an inheritance relationship like the one in Figure 12.5:
2. You need to create the dependency and use it to tell Dagger which @Component or
@Subcomponent you’re inheriting from. As you’ll see very soon, you do this using
a tool you’ve used before: factory methods.
4. Update the way you create the @Components for the different @Scopes.
raywenderlich.com 307
Dagger by Tutorials Chapter 12: Components Dependencies
Note: As often happens, you won’t be able to successfully build the app until
the end of the migration. Just code along and everything will be fine.
For your next step, you’ll start identifying which @Components should be
@Subcomponents.
// 1
@Subcomponent(
modules = [ActivityModule::class]
// 2
)
@ActivityScope
interface ActivityComponent {
// ...
}
// 1
@Subcomponent(
modules = [FragmentModule::class]
// 2
)
@FragmentScope
interface FragmentComponent {
// ...
}
raywenderlich.com 308
Dagger by Tutorials Chapter 12: Components Dependencies
Usually, you’d just need to provide a factory method for the @Subcomponent. For
ApplicationComponent, you’d add something like this:
@Component(modules = [ApplicationModule::class])
@ApplicationScope
interface ApplicationComponent {
// ...
}
@Component(modules = [ApplicationModule::class])
@ApplicationScope
interface ApplicationComponent {
It might be a little weird to have a factory method that returns a factory, but that’s
exactly what the code above does. :]
raywenderlich.com 309
Dagger by Tutorials Chapter 12: Components Dependencies
You now need to do the same for ActivityComponent but, in this case, the
FragmentComponent doesn’t need any existing objects other than the one it inherits
from the other @Components.
@Subcomponent(
modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {
// ...
fun fragmentComponent(): FragmentComponent // HERE
}
Because of this change in the way you create the @Components, you now need to
make some changes to the Factory and Builder.
@Subcomponent(
modules = [FragmentModule::class]
)
@FragmentScope
interface FragmentComponent {
raywenderlich.com 310
Dagger by Tutorials Chapter 12: Components Dependencies
The same isn’t true for ActivityComponent, because it needs an Activity that it
doesn’t inherit from its parent, ApplicationComponent. To handle this, open
ActivityComponent.kt in di and apply the following change:
@Subcomponent(
modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {
// ...
@Subcomponent.Factory // 1
interface Factory {
fun create(
@BindsInstance activity: Activity
// 2
): ActivityComponent
}
}
You can assume that ActivityComponent already received the objects from its
parent, ApplicationComponent, so the Factory’s create() only needs one
parameter. Because it’s a @Subcomponent, Dagger asks you to use either
@Subcomponent.Factory or the dual @Subcomponent.Builder. Therefore, in the
code above, you:
Very good! Now Dagger knows how Busso’s @Components and @Subcomponents relate
to one another.
Keep in mind that if you build the app now, you’ll get some errors. That’s because
Dagger generates something different than you had before, and you need to update
the way you create the different @Components and @Subcomponents.
raywenderlich.com 311
Dagger by Tutorials Chapter 12: Components Dependencies
• ApplicationComponent
• ActivityComponent
• FragmentComponent
Of course, the build still fails because you need to update your code accordingly.
appComponent = DaggerApplicationComponent
.factory()
.create(this)
}
}
It’s good to see that in this case, you don’t have to do anything. :] That’s because you
didn’t change the way you create ApplicationComponent. Just like before, you need
to create an instance of ApplicationComponent to save in the appComponent
property you expose using the appComp extension property.
Now, you need to check how you create the instance of the ActivityComponent.
raywenderlich.com 312
Dagger by Tutorials Chapter 12: Components Dependencies
• SplashActivity
• MainActivity
Open SplashActivity.kt from the ui.view.splash package and apply the following
change:
Note: During this migration, you’ll need to delete the missing imports, which
Android Studio displays in red.
raywenderlich.com 313
Dagger by Tutorials Chapter 12: Components Dependencies
Now, you need to do the same for MainActivity. Open MainActivity.kt from
ui.view.main and apply the following:
In this case, remember that you also save the ActivityComponent reference in the
comp extended variable.
• BusStopFragment
• BusArrivalFragment
The change you need to make is basically the same as what you did for
ActivityComponent above. Open BusStopFragment.kt in ui.view.busstop and
apply the following change:
raywenderlich.com 314
Dagger by Tutorials Chapter 12: Components Dependencies
Now, you can finally build and run the app and see something similar to Figure 12.6:
raywenderlich.com 315
Dagger by Tutorials Chapter 12: Components Dependencies
• ApplicationComponent
• ActivityComponent
Open ApplicationComponent.kt in di and remove locationObservable() and
bussoEndpoint(). The content of this file becomes:
@Component(modules = [ApplicationModule::class])
@ApplicationScope
interface ApplicationComponent {
@Component.Factory
interface Builder {
@Subcomponent(
modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {
@Subcomponent.Factory
interface Factory {
fun create(
@BindsInstance activity: Activity
): ActivityComponent
raywenderlich.com 316
Dagger by Tutorials Chapter 12: Components Dependencies
}
}
Build and run the app to check that everything still works as expected. This proves
that those factory methods are no longer necessary.
Using @Subcomponent.Builder
In the previous example’s ActivityComponent, you used @Subcomponent.Factory,
but you could have used @Subcomponent.Builder, instead. To prove this, open
ActivityComponent.kt from di and apply the following change:
@Subcomponent(
modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {
// ...
@Subcomponent.Builder // 1
interface Builder {
// 2
fun activity(
@BindsInstance activity: Activity
): Builder
// 3
fun build(): ActivityComponent
}
}
Here you:
2. Add activity() to provide the reference to the Activity you need. This
function must return the reference to the Builder itself.
Because you’re now using a Builder instead of a Factory, you also need to update
ApplicationComponent. Open ApplicationComponent.kt in di and replace the
existing activityComponentFactory() with the following:
@Component(modules = [ApplicationModule::class])
@ApplicationScope
interface ApplicationComponent {
// ...
fun activityComponentBuilder(): ActivityComponent.Builder //
HERE
raywenderlich.com 317
Dagger by Tutorials Chapter 12: Components Dependencies
// ...
}
This tells Dagger that you need a Builder to create ActivityComponent from
ApplicationComponent.
Finally, you need to update the code when you create ActivityComponent. Open
SplashActivity.kt in ui.view.splash and change the following code:
raywenderlich.com 318
Dagger by Tutorials Chapter 12: Components Dependencies
mainPresenter.goToBusStopList()
}
}
}
Now, build and run Busso and make sure that everything works as expected.
2. @Subcomponents
But which one is the best? As always, there’s no answer that works in every case.
Each solution has its pros and cons.
• You need to use factory methods to explicitly publish the objects that a
@Component wants to share with others.
• When you have existing objects, you can use @Component.Builder and
@Component.Factory.
• As you’ll see in the next chapter, multibinding doesn’t work when you manage
@Component dependencies using the dependencies attribute.
raywenderlich.com 319
Dagger by Tutorials Chapter 12: Components Dependencies
• You create @Subcomponent instances using factory methods you define in the
parent @Component or @Subcomponent.
• When you have existing objects, you can use @Subcomponent.Builder and
@Subcomponent.Factory. The parent will define factory methods for them.
In the following chapters, you can use either solution unless limitations are in place.
Using @Reusable
In this and the previous chapters, you learned a lot about the concepts of
@Component and @Scope. In particular, you learned how to create a custom @Scope
that’s not much different than the existing @Singleton.
@Scope allows you to control the number of instances Dagger creates for the
injection of a specific type. You can create a new instance for a specific type any time
you need to inject it or you can ask Dagger to create only one that is bound to the
@Component it belongs to. As you’ve read many times now, the lifespan of a scoped
object is bound to the lifespan of the @Component with the same scope.
Sometimes, for performance reasons, you might need to limit the number of
instances for a given type without binding those instances to the lifespan of a specific
@Component. If you want Dagger to check if a convenient instance of an object
already exists before creating a new one, you can use @Reusable.
@Documented
@Beta
@Retention(RUNTIME)
@Scope
public @interface Reusable {}
raywenderlich.com 320
Dagger by Tutorials Chapter 12: Components Dependencies
As you can see, @Reusable is a @Scope that Dagger treats in a particular and
privileged way. You don’t need to use any objects with @Reusable scope for Busso,
but it’s interesting to note how Dagger manages them.
Note: As you can see in the previous code, @Reusable is in @Beta, so the logic
Dagger uses for it might change in future releases.
• @Component1 defines the binding between two objects with types A and B.
• @Component2 does the same for two objects with types C and D.
@Component4 and @Component3 define the bindings for objects of the same type R,
but the objects are bound to different @Scopes.
raywenderlich.com 321
Dagger by Tutorials Chapter 12: Components Dependencies
At this point, you’d consider whether you can reuse the objects of type R. If so, you
can bind them to the @Reusable scope and Dagger will treat them as if they were
bound to the scope for @Component1. This is the scope of the least common
ancestor @Component.
In this case, you’d consider the pros and cons of @Reusable scopes. For a modern
JVM implementation running on a desktop or server, creating and destroying objects
isn’t a big issue. On mobile, things are different because the Garbage Collector can
impact an app’s performance. @Reusable is an interesting tool you should always
consider, but it isn’t the best choice in every situation.
Wow! This has been another dense and important chapter. In it, you learned one of
the most difficult topics in Dagger: how to implement @Component dependencies
using @Subcomponents. This chapter also concludes Section Three of this book.
Congratulations!
Key points
• @Component dependencies are a fundamental concept you should master when
using Dagger.
• Using the @Component dependencies attribute requires you to explicitly expose the
objects you want to share using factory methods. The dependency is not
transitive.
In the next chapter, you’ll learn all about a very important and useful Dagger
feature: multibindings.
raywenderlich.com 322
Section IV: Advanced Dagger
In this section, you’ll dive deeper into the advanced features of Dagger like multi-
binding. Multibinding is a very interesting feature of Dagger because it simplifies the
integration of new features using a plugin pattern you’ll learn in this section.
You’ll implement a simple framework that allows you to integrate new services in
the Busso app in a very simple and declarative way. You’ll learn all you need to know
about multi-binding with Set and Map.
raywenderlich.com 323
13 Chapter 13: Multibinding
By Massimo Carli
In the previous sections, you learned how Dagger works by migrating the Busso app
from a homemade injection framework based on the ServiceLocator pattern to a
fully scoped Dagger app. Great job!
Now, it’s time to use what you’ve learned to add a new feature to the Busso App,
which will display messages from different endpoints at the top of the BusStop
screen. For instance, you can display information about the weather and any traffic
problems at your destination.
You also want to let the user add or remove new endpoints in a simple and
declarative way. To do this, you’ll implement a small framework called an
information plugin framework. Its high-level architecture looks like this:
raywenderlich.com 324
Dagger by Tutorials Chapter 13: Multibinding
In this chapter’s starter project, you’ll find a version of Busso that already contains
the first implementation of the information plugin framework.
You can already see this very simple feature at work. Right now, it displays the
coordinates of your current location:
raywenderlich.com 325
Dagger by Tutorials Chapter 13: Multibinding
When you open the Busso project with Android Studio, you’ll see the source
directory structure in Figure 13.3:
• model: A very simple class that models the information you’ll receive from the
server.
• whereami: The classes you’ll need to implement the feature that displays the
coordinates of your current location, as shown in Figure 13.3. This is the first
feature that uses the information plugin framework.
An in-depth description of all the code would take too much space and time, so in
this chapter, you’ll focus on the aspects related to dependency injection and, of
course, Dagger.
raywenderlich.com 326
Dagger by Tutorials Chapter 13: Multibinding
Dagger configuration
The main aspect of this framework is the Dagger configuration you find in the
plugins.di package. Open InformationPluginModule.kt in plugins.di and look at
its code:
interface InformationPluginModule { // 1
@Module
interface ApplicationBindings { // 1
@Binds
fun bindInformationPluginRegistry(
impl: InformationPluginRegistryImpl // 2
): InformationPluginRegistry
}
@Module
interface FragmentBindings { // 1
@Binds
fun bindInformationPluginPresenter(
impl: InformationPluginPresenterImpl // 3
): InformationPluginPresenter
@Binds
fun bindInformationPluginViewBinder(
impl: InformationPluginViewBinderImpl // 3
): InformationPluginViewBinder
}
}
This is the code of a @Module that contains all the bindings for the information
plugin framework. These definitions give you insight into many important aspects of
the framework. In particular:
2. The framework has just one object that has @ApplicationScope: The
InformationPluginRegistry, which contains the definitions for the plugins you
want to use in the app. As you’ll see, this will need a way to describe the plugin to
the framework and to register it.
3. The bindings with @FragmentScope are related to presenter and view binder,
which you can see directly in the source code in the project.
raywenderlich.com 327
Dagger by Tutorials Chapter 13: Multibinding
InformationPluginRegistry
When it comes to working with plugins, frameworks, including the information
plugin framework, have some important characteristics in common. They all need to:
That’s why you need an abstraction to handle the registry responsibilities. In your
case, this is InformationPluginRegistry, whose contents you can see in
InformationPluginRegistry.kt in plugins.api:
interface InformationPluginRegistry {
In the same interface, you can also see that you describe each plugin instance using
an InformationPluginSpec. Open InformationPluginSpec.kt in plugins.api and
look at its code:
interface InformationPluginSpec {
2. A name.
raywenderlich.com 328
Dagger by Tutorials Chapter 13: Multibinding
@ApplicationScope
class InformationPluginRegistryImpl @Inject constructor() :
InformationPluginRegistry {
1. Create an endpoint.
Look in the starter project in the material for this chapter to find the WhereAmI
information plugin and you’ll see that it has each of these steps already completed
for you. Here’s a closer look at how they work for the WhereAmI feature.
Creating an endpoint
Assuming that you have an actual endpoint to call, your first step is to define an
implementation of InformationEndpoint, which you find in plugins.api.
raywenderlich.com 329
Dagger by Tutorials Chapter 13: Multibinding
interface InformationEndpoint {
interface MyLocationEndpoint {
@GET("${BUSSO_SERVER_BASE_URL}myLocation/{lat}/{lng}")
fun whereAmIInformation(
@Path("lat") latitude: Double,
@Path("lng") longitude: Double
): Single<InfoMessage>
}
raywenderlich.com 330
Dagger by Tutorials Chapter 13: Multibinding
The UML diagram in Figure 13.4 gives you a better idea of the relationship between
the different endpoint abstractions:
@Module(includes = [WhereAmIModule.Bindings::class])
object WhereAmIModule {
@Provides
@ApplicationScope // 1
fun provideMyLocationEndpoint(retrofit: Retrofit):
MyLocationEndpoint {
return retrofit.create(MyLocationEndpoint::class.java)
}
@Provides
@ApplicationScope // 2
fun provideWhereAmISpec(endpoint: WhereAmIEndpointImpl):
InformationPluginSpec = object : InformationPluginSpec {
override val informationEndpoint: InformationEndpoint
raywenderlich.com 331
Dagger by Tutorials Chapter 13: Multibinding
get() = endpoint
override val serviceName: String
get() = "WhereAmI"
}
@Module
interface Bindings { // 3
@Binds
fun bindWhereAmIEndpoint(
impl: WhereAmIEndpointImpl
): WhereAmIEndpoint
}
}
The most important thing here is that you create InformationPluginSpec for the
WhereAmI plugin, which completes the third step in the previous TODO list.
Now, you can also see how WhereAmIModule is one of the modules that
ApplicationComponent uses for the bindings. Inside ApplicationComponent.kt in
di, you’ll see:
@Component(modules = [
ApplicationModule::class,
InformationPluginModule.ApplicationBindings::class,
WhereAmIModule::class // HERE
])
@ApplicationScope
interface ApplicationComponent {
// ...
}
raywenderlich.com 332
Dagger by Tutorials Chapter 13: Multibinding
Registering InformationPluginSpec
This is the last and most important step in the process. Once you define
InformationPluginSpec, you need to register it to InformationPluginRegistry to
make it available to the framework. At the moment, you do this in Main.kt, which
looks like this:
@Inject
lateinit var informationPluginRegistry:
InformationPluginRegistry // 1
@Inject
lateinit var whereAmISpec: InformationPluginSpec // 2
raywenderlich.com 333
Dagger by Tutorials Chapter 13: Multibinding
Now you can finally build and run, getting what’s in Figure 13.5:
raywenderlich.com 334
Dagger by Tutorials Chapter 13: Multibinding
But is all this scaffolding really necessary? The answer is, obviously, no.
Dagger multibinding solves this problem very simply, by letting you create a Set or a
Map from some Dagger bindings in a declarative and pluggable way.
That’s all! In your next step, you’ll make a small refactor that will have a big impact.
Refactoring InformationPluginRegistry
You want all the InformationPluginSpecs you register to be in a
Set<LocationPluginInfo>, so you need to change InformationPluginRegistry
accordingly. To do this, open InformationPluginRegistryImpl.kt in plugins.impl
and apply the following changes:
@ApplicationScope
class InformationPluginRegistryImpl @Inject constructor(
private val informationPlugins:
Set<InformationPluginSpec> // 1
) : InformationPluginRegistry {
// 2
override fun plugins(): List<InformationPluginSpec> =
informationPlugins.toList() // 3
}
raywenderlich.com 335
Dagger by Tutorials Chapter 13: Multibinding
2. You no longer need register() because you’re asking Dagger to register the
plugins for you. Dagger will do this by putting your plugin spec into
Set<InformationPluginSpec> directly. For the same reason, you don’t need
plugins anymore, so you delete them both.
The second point means you should also delete register() from the interface.
Open InformationPluginRegistry.kt in plugins.api and change it to the following:
interface InformationPluginRegistry {
Registering WhereIAm
This is the most interesting part: How can you register your plugin declaratively?
Open WhereAmIModule.kt in plugins.whereami.di and you’ll see that you’re
already telling Dagger how get an InformationPluginSpec for the WhereAmI plugin:
@Module(includes = [WhereAmIModule.Bindings::class])
object WhereAmIModule {
// ...
@Provides
@ApplicationScope
fun provideWhereAmISpec(endpoint: WhereAmIEndpointImpl):
InformationPluginSpec = object : InformationPluginSpec {
override val informationEndpoint: InformationEndpoint
get() = endpoint
override val serviceName: String
get() = "WhereAmI"
}
// ...
}
raywenderlich.com 336
Dagger by Tutorials Chapter 13: Multibinding
What you’re not doing is telling Dagger to put that object into a
Set<InformationPluginSpec>. Changing this is as simple as adding @IntoSet to
the previous @Provides declaration, as in the following code:
@Module(includes = [WhereAmIModule.Bindings::class])
object WhereAmIModule {
// ...
@Provides // 1
@ApplicationScope // 2
@IntoSet // 3 HERE
fun provideWhereAmISpec(endpoint: WhereAmIEndpointImpl):
InformationPluginSpec = object : InformationPluginSpec { // 4
override val informationEndpoint: InformationEndpoint
get() = endpoint
override val serviceName: String
get() = "WhereAmI"
}
// ...
}
You won’t believe it, but this is all you have to do to configure multibinding.
Note: Spoiler alert! This isn’t completely true. :] There’s one thing you need to
fix first, and you’ll see what that is soon.
2. @ApplicationScope to tell Dagger that you want to bind just one instance of
InformationPluginSpec to the ApplicationComponent lifecycle.
Before building and running the app, you need to clean up a few things.
raywenderlich.com 337
Dagger by Tutorials Chapter 13: Multibinding
Now, you can build and run. Oops! Something went wrong.
Using @JvmSuppressWildcards
When you build now, you get the following error:
What’s going on? It looks like Dagger can’t find the object to inject into
InformationPluginRegistry.
raywenderlich.com 338
Dagger by Tutorials Chapter 13: Multibinding
@Provides
@ApplicationScope
@IntoSet
fun provideWhereAmISpec(endpoint: WhereAmIEndpointImpl):
InformationPluginSpec = object : InformationPluginSpec {
override val informationEndpoint: InformationEndpoint
get() = endpoint
override val serviceName: String
get() = "WhereAmI"
If you look carefully at the error message, however, you see that Dagger is looking for
something other than Set<InformationPluginSpec>. It’s looking for a binding with
a type of Set<? extends InformationPluginSpec>.
In binding declarations, types are very important to Dagger. The IS-A relationship
doesn’t work unless it’s explicit. That means that if you define a binding for the type
MyType, Dagger will look for an object of type MyType and not for a type that IS-A
MyType.
You’ve seen this many times. If you define a binding for a type
InformationPluginRegistry, Dagger won’t use an implementation like
InformationPluginRegistryImpl unless you explicitly define it using @Binds or
@Provides.
But why is Dagger looking for Set<? extends InformationPluginSpec> when you
specified Set<InformationPluginSpec> as InformationPluginRegistryImpl’s
primary constructor parameter type? Well, that’s Kotlin’s fault. :]
Kotlin converts any generic type into a Java generic with wildcards. That turns
Set<InformationPluginSpec> into Set<? extends InformationPluginSpec>
because Set<A> is covariant. If it would be contra-variant, a type MyType<in A>
would become MyType<? super A>.
raywenderlich.com 339
Dagger by Tutorials Chapter 13: Multibinding
Implementing @JvmSuppressWildcard
To implement this, open InformationPluginRegistryImpl.kt in plugins.impl and
apply this change:
@ApplicationScope
class InformationPluginRegistryImpl @Inject constructor(
private val informationPlugins: @JvmSuppressWildcards
Set<InformationPluginSpec> // HERE
) : InformationPluginRegistry {
You could also use this as the annotation for the specific generic type parameter, if
you needed to control each type parameter independently, like this:
@ApplicationScope
class InformationPluginRegistryImpl @Inject constructor(
private val informationPlugins: Set<@JvmSuppressWildcards
InformationPluginSpec> // HERE
) : InformationPluginRegistry {
Now, you can finally build and run the app successfully. Just like before, you’ll see
something like this:
raywenderlich.com 340
Dagger by Tutorials Chapter 13: Multibinding
In this example, you’ll use a simple service from the Busso Server that lets you print
a random weather condition message. At this point, it’s a simple text, but of course,
you can extend the feature as you want.
Defining an endpoint
Create a new package, plugins.weather.endpoint, then add a new file named
WeatherEndpoint.kt to it with the following code:
interface WeatherEndpoint {
@GET("${BUSSO_SERVER_BASE_URL}weather/{lat}/{lng}")
fun fetchWeatherCondition(
@Path("lat") latitude: Double,
@Path("lng") longitude: Double
): Single<InfoMessage>
}
raywenderlich.com 341
Dagger by Tutorials Chapter 13: Multibinding
@Module(includes = [WeatherModule.Bindings::class])
object WeatherModule {
@Provides
@ApplicationScope
fun provideWeatherEndpoint(retrofit: Retrofit):
WeatherEndpoint {
return retrofit.create(WeatherEndpoint::class.java)
}
@Provides
@IntoSet // HERE
@ApplicationScope
fun provideWeatherSpec(endpoint: WeatherInformationEndpoint):
InformationPluginSpec = object : InformationPluginSpec {
override val informationEndpoint: InformationEndpoint
get() = endpoint
override val serviceName: String
get() = "Weather"
@Module
interface Bindings {
raywenderlich.com 342
Dagger by Tutorials Chapter 13: Multibinding
@Binds
fun bindWeatherInformationEndpoint(
impl: WeatherInformationEndpointImpl
): WeatherInformationEndpoint
}
}
This follows the same structure as the WhereAmIModule you saw for the WhereAmI
information plugin. The important part is that you use @IntoSet for
provideWeatherSpec().
@Component(modules = [
ApplicationModule::class,
InformationPluginModule.ApplicationBindings::class,
WhereAmIModule::class,
WeatherModule::class // HERE
])
@ApplicationScope
interface ApplicationComponent {
// ...
}
raywenderlich.com 343
Dagger by Tutorials Chapter 13: Multibinding
This shows the top part of the screen only, to save space.
This is cool, right? But there’s a problem… in addition to the stormy weather here in
London now. If you launch the app another time or you force BusStopFragment to
reload, you might see the following output:
However, this wouldn’t actually be simple, because all the requests to the different
endpoints are asynchronous. Just because you sort how you send the requests
doesn’t mean you’ll receive the responses in the same order.
You can use what you’ll learn in the following paragraphs to solve the ordering
problem as an exercise.
raywenderlich.com 344
Dagger by Tutorials Chapter 13: Multibinding
It’s worth mentioning that the Set Dagger creates is immutable. Dagger defines the
content of the Set when it creates the dependency graph and you can’t change it
later. But nobody’s preventing you from using a Set<Provider<A>> or
Set<Lazy<A>> if you don’t want to create everything when the app starts.
You have another option, though: defining all the InformationPluginSpec in the
same place. Try this by creating a new file named InformationSpecsModule.kt in
plugins.di and add the following code:
@Module(
includes = [
WhereAmIModule::class,
WeatherModule::class
]
)
object InformationSpecsModule {
@Provides
@ElementsIntoSet // 1
@ApplicationScope
fun provideWeatherSpec(
@Named(WHEREAMI_INFO_NAME) whereAmISpec:
InformationPluginSpec, // 2
@Named(WEATHER_INFO_NAME) weatherSpec:
InformationPluginSpec // 2
): Set<InformationPluginSpec> { // 3
return mutableSetOf<InformationPluginSpec>().apply {
add(whereAmISpec)
add(weatherSpec)
}
}
}
raywenderlich.com 345
Dagger by Tutorials Chapter 13: Multibinding
@Module(includes = [WhereAmIModule.Bindings::class])
object WhereAmIModule {
// ...
@Provides
@ApplicationScope
// 2
@Named(WHEREAMI_INFO_NAME) // 3
fun provideWhereAmISpec(endpoint: WhereAmIEndpointImpl):
InformationPluginSpec = object : InformationPluginSpec {
override val informationEndpoint: InformationEndpoint
get() = endpoint
override val serviceName: String
get() = WHEREAMI_INFO_NAME
}
}
2. Removed @IntoSet.
@Module(includes = [WeatherModule.Bindings::class])
object WeatherModule {
// ...
@Provides
@Named(WEATHER_INFO_NAME)
@ApplicationScope
fun provideWeatherSpec(endpoint: WeatherInformationEndpoint):
InformationPluginSpec = object : InformationPluginSpec {
override val informationEndpoint: InformationEndpoint
get() = endpoint
override val serviceName: String
get() = WEATHER_INFO_NAME
}
}
raywenderlich.com 346
Dagger by Tutorials Chapter 13: Multibinding
@Component(modules = [
ApplicationModule::class,
InformationPluginModule.ApplicationBindings::class,
InformationSpecsModule::class
])
@ApplicationScope
interface ApplicationComponent {
@Component.Factory
interface Builder {
Now, just build and run and check that everything works as expected.
raywenderlich.com 347
Dagger by Tutorials Chapter 13: Multibinding
Wow! This has been a very dense chapter. You learned a lot about multibinding and
you managed to implement a simple framework that allows you to integrate new
services in Busso in a simple and declarative way. Multibinding uses more than just
Set.
In the next chapter, you’ll learn everything you need to know about multibinding
with Map.
Key points
• Dagger multibinding allows you to add functionality to your app in an easy and
declarative way.
• @IntoSet allows you to populate a Set when you initialize the dependency graph.
• @ElementsIntoSet allows you to put more than one object into a multibinding
Set.
raywenderlich.com 348
14 Chapter 14: Multibinding
With Maps
By Massimo Carli
In the previous chapter, you learned how to use multibinding with Set by
implementing a simple framework to integrate information from remote endpoints
into the Busso App. By refactoring the Information Plugin Framework, you saw
how to dynamically add features to Busso in a simple and declarative way.
• Use fundamental type keys with @StringKey, @ClassKey, @IntKey and @LongKey.
raywenderlich.com 349
Dagger by Tutorials Chapter 14: Multibinding With Maps
In any case, Dagger offers you another option: multibinding with Map. Map is a data
structure that allows you map a value to a key.
Note: If you want to know more about data structures in Kotlin and crack
interviews for getting your dream job, have a look at Data Structures &
Algorithms in Kotlin (https://www.raywenderlich.com/books/data-structures-
algorithms-in-kotlin).
In the following paragraph, you’ll see how to use multibinding with Map<K, V>
where the key, K, is one of the following:
• Class<T>
• Custom type
Using @StringKey
For your first example, suppose you want to simplify InformationPluginSpec by
removing the property name and giving the plugin a name when you add it to the
registry.
interface InformationPluginSpec {
raywenderlich.com 350
Dagger by Tutorials Chapter 14: Multibinding With Maps
@ApplicationScope
class InformationPluginRegistryImpl @Inject constructor(
private val informationPlugins: @JvmSuppressWildcards
Map<String, InformationPluginSpec> /// 1
) : InformationPluginRegistry {
@Module(
includes = [
WhereAmIModule::class,
WeatherModule::class
]
)
object InformationSpecsModule
Note: You could also delete it and restore the state of the app before More
about Multibinding with Set: @ElementsIntoSet. However, simply deleting
the previous definition is fine.
raywenderlich.com 351
Dagger by Tutorials Chapter 14: Multibinding With Maps
@ApplicationScope
@IntoMap // 1
@StringKey(WEATHER_INFO_NAME) // 2
fun provideWeatherSpec(endpoint: WeatherInformationEndpoint):
InformationPluginSpec = object : InformationPluginSpec {
override val informationEndpoint: InformationEndpoint
get() = endpoint
}
// ...
}
1. @IntoMap to tell Dagger that the object you @Provides is part of a multibinding
definition with a Map.
raywenderlich.com 352
Dagger by Tutorials Chapter 14: Multibinding With Maps
Note: Dagger also allows keys of type Int and Long. You can easily try them
out on your own by using @IntKey and @LongKey and repeating what you just
did for @StringKey.
Using @StringKey, you just set the name of the information plugin as the key of the
Map you’re using to multibind. In this case, you’re not actually using that
information, but you’ll see how to use key information in a more complex example
next.
Using @ClassKey
Another type of key Dagger gives you is Class<T>. You can use it the same way you
used String, but for your next step, you’ll try something more ambitious, instead.
You’ll use code that’s similar to the information plugins you implemented earlier.
The main difference is in InformationEndpoint — the rest is just scaffolding.
raywenderlich.com 353
Dagger by Tutorials Chapter 14: Multibinding With Maps
interface InformationPluginSpec {
val serviceName: String
}
interface InformationPluginRegistry {
fun plugins(): List<InformationEndpoint>
}
Here, you’re just trying to get a list of InformationEndpoints, and that’s what
InformationPluginRegistry provides. The magic is in its implementation.
@ApplicationScope
class InformationPluginRegistryImpl @Inject constructor(
private val retrofit: Retrofit, // 1
informationPlugins: @JvmSuppressWildcards Map<Class<*>,
InformationPluginSpec> // 2
) : InformationPluginRegistry {
raywenderlich.com 354
Dagger by Tutorials Chapter 14: Multibinding With Maps
1. Add Retrofit as the primary constructor parameter. This is the object you need
to create the actual InformationEndpoint.
interface InformationEndpoint {
This gives you two parameters that are consistent with the specific Retrofit
interfaces.
raywenderlich.com 355
Dagger by Tutorials Chapter 14: Multibinding With Maps
Here you:
@GET("${BUSSO_SERVER_BASE_URL}weather/{lat}/{lng}")
override fun fetchInformation(
@Path("lat") latitude: Double,
@Path("lng") longitude: Double
): Single<InfoMessage>
}
raywenderlich.com 356
Dagger by Tutorials Chapter 14: Multibinding With Maps
}
}
Here, you:
1. Use @IntoMap to tell Dagger to put the object you @Provide in a Map with values
of type InformationPluginSpec.
3. Remove endpoint, which you don’t need anymore because the same information
is in the @ClassKey attribute.
More importantly, WhereAmIModule contains just one definition. The others are no
longer necessary because the registry now creates the endpoints.
1. Use @IntoMap to tell Dagger to put the object you @Provide in a Map with values
of type InformationPluginSpec.
3. Remove endpoint, which you don’t need anymore because the same information
is in the @ClassKey attribute.
raywenderlich.com 357
Dagger by Tutorials Chapter 14: Multibinding With Maps
What you get from the InformationPluginRegistry and the way you invoke the
InformationEndpoint are now different.
• WeatherInformationEndpoint
• WeatherInformationEndpointImpl
• WhereAmIEndpoint
• WhereAmIEndpointImpl
Now you’re ready to test the Busso app.
raywenderlich.com 358
Dagger by Tutorials Chapter 14: Multibinding With Maps
The question now is: Can you do even better? You’ll make more improvements in the
next section.
This is useful when you want to give Dagger additional information when you
implement the multibindings, as you’ll see also when you’ll learn how Dagger works
with Google Architecture Components. In this scenario, Dagger provides the
following @MapKey annotation:
@Documented
@Target(ANNOTATION_TYPE) // 1
raywenderlich.com 359
Dagger by Tutorials Chapter 14: Multibinding With Maps
@Retention(RUNTIME)
public @interface MapKey {
2. It has an attribute, unwrapValue, that has a default value of true. You’ll learn
more about it soon, but in short, it tells Dagger whether or not the key is
complex.
A code example will help you to better understand how all this works.
As an example, suppose you want to do what you did with @KeyClass — provide
Class<InformationEndpoint> for the endpoint of an information plugin. But this
time, you want to use a custom type as the key.
The first step is to create a new file named SimpleInfoKey.kt in plugins.api with
the following code:
@MapKey // 1
annotation class SimpleInfoKey(
val endpointClass: KClass<*> // 2
)
Next, you need to replace the current @ClassKey with the custom annotation
@SimpleInfoKey. Open WeatherModule.kt in plugins.weather.di and apply the
following change:
@Module
object WeatherModule {
raywenderlich.com 360
Dagger by Tutorials Chapter 14: Multibinding With Maps
@Provides
@ApplicationScope
@IntoMap
@SimpleInfoKey(WeatherEndpoint::class) // HERE
fun provideWeatherSpec(): InformationPluginSpec = object :
InformationPluginSpec {
override val serviceName: String
get() = WEATHER_INFO_NAME
}
}
@Module
object WhereAmIModule {
@Provides
@ApplicationScope
@IntoMap
@SimpleInfoKey(MyLocationEndpoint::class) // HERE
fun provideWhereAmISpec(): InformationPluginSpec = object :
InformationPluginSpec {
override val serviceName: String
get() = WHEREAMI_INFO_NAME
}
}
And that’s it. You don’t have to do anything except build and run the app and it will
work as expected.
When you use @MapKey to define a custom key that lets you use multibinding with
Map, and unwrapValue() has a default value of true, it means that:
• Your key can only have a single property. In the previous example, it was
endpointClass.
• The type for the key is same as the type for that single property — in this case,
KClass<*>. That’s why you didn’t have to change
InformationPluginRegistryImpl.
raywenderlich.com 361
Dagger by Tutorials Chapter 14: Multibinding With Maps
@MapKey(unwrapValue = false) // 1
annotation class ComplexInfoKey(
val endpointClass: @JvmSuppressWildcards KClass<out
InformationEndpoint>, // 2
val name: String // 2
)
In this case, the annotation has false as unwrapValue’s value, which means that the
type for the key is now the annotation itself.
It’s important to say that, if you want to use a complex @MapKey with false for the
unwrapValue attribute, Dagger requires an additional dependency.
Add it by opening build.gradle for the main app module and adding the following
definitions in the dependencies block:
implementation "com.google.auto.value:auto-value-annotations:
$autovalue_annotation_version"
kapt "com.google.auto.value:auto-value:$autovalue_version"
2. Configure the annotation processor the auto value library needs for the code
generation.
Note: The values for the version parameters are already available in
versions.gradle in the root of the project.
raywenderlich.com 362
Dagger by Tutorials Chapter 14: Multibinding With Maps
@Module
object WeatherModule {
@Provides
@ApplicationScope
@IntoMap
@ComplexInfoKey( // 1
WeatherEndpoint::class,
WEATHER_INFO_NAME
)
fun provideWeatherSpec(): InformationPluginSpec =
InformationPluginSpec
}
object InformationPluginSpec
@Module
object WhereAmIModule {
@Provides
@ApplicationScope
@IntoMap
@ComplexInfoKey(
MyLocationEndpoint::class,
WHEREAMI_INFO_NAME
)
fun provideWhereAmISpec(): InformationPluginSpec =
InformationPluginSpec
}
When you use a complex key like this, you also need to change Map’s type, which now
becomes the annotation type.
raywenderlich.com 363
Dagger by Tutorials Chapter 14: Multibinding With Maps
@ApplicationScope
class InformationPluginRegistryImpl @Inject constructor(
private val retrofit: Retrofit,
informationPlugins: @JvmSuppressWildcards
Map<ComplexInfoKey, InformationPluginSpec> // 1
) : InformationPluginRegistry {
1. Make ComplexInfoKey the type for the key in the Map you inject as the primary
constructor parameter.
Now you can finally build and successfully run the Busso App.
raywenderlich.com 364
Dagger by Tutorials Chapter 14: Multibinding With Maps
Key points
• Dagger allows you to use multibinding with a Map.
• When you use a Map for multibinding, you can use keys of the following types:
String, Int, Long and KClass.
• If you need more informative keys, @KeyMap allows you to create custom types,
which you can use in a simple or complex way.
• If you use @KeyMap and an unwrapValue attribute with a default value of true, the
type of the key is the type of the unique property of your custom key.
• A @KeyMap is complex if the value for the unwrapValue attribute is false. In this
case, you need to add an auto-value as a dependency in your project.
• You must use a complex @KeyMap for the key of the Map you use in multibinding.
Wow! It’s been a very interesting chapter. Using multibinding with Map, you managed
to improve the Information Plugin Framework architecture even further.
But, what if you want a more structured and organized set of modules? In the next
chapter, you’ll learn how Dagger can help you.
raywenderlich.com 365
15 Chapter 15: Dagger &
Modularization
By Massimo Carli
In the previous chapters, you learned about multibinding with Sets and Maps to
improve the architecture of the Information Plugin Framework. With that
information, you can add new features to the Busso App in an easy, pluggable and
declarative way.
You’ve vastly improved Busso’s architecture, but there’s still room to make it even
better. For instance, all the code is currently in the main app module. It would be
nice to split that code into different modules to reduce the building time of your app
while increasing its reusability and extensibility. But how can you do that with
Dagger? Is it even possible?
The answer, of course, is yes! In this chapter, you’ll refactor the Busso App by moving
some of the code from the main app module to other modules and changing the
Dagger configuration accordingly.
Note: In this chapter, you’ll read the name module many times, referring to
either the Gradle Module or the Dagger Module. To make everything clear,
this chapter will refer to Gradle Modules as modules and Dagger Modules as
@Modules.
raywenderlich.com 366
Dagger by Tutorials Chapter 15: Dagger & Modularization
What is modularization?
Modularization is the process of splitting the code and resources of your app into
separate, smaller modules — in this case, Gradle modules. You can think of a Gradle
module as a way to encapsulate code and resources. This makes it simpler to create
a single library that you either use locally or publish in a repository like Artifactory
to share with other developers.
A module might depend on other modules that you declare in the dependencies
block of its build.gradle.
Note: You’ll use Gradle modules in this chapter, but the same concepts are
valid with other systems, like Apache Maven or Apache Ivy.
Whether there’s a big advantage to using different modules in your app depends on
how big and complex that app is. In general, using different modules gives you the
following benefits:
• The opportunity to create libraries that make the code more reusable.
This chapter will cover each point in more detail, giving you the chance to improve
Busso along the way.
Start by opening the Busso App from the starter folder of the materials for this
chapter in Android Studio.
raywenderlich.com 367
Dagger by Tutorials Chapter 15: Dagger & Modularization
At the moment, the new modules are all empty. To make your job easier, they already
contain the dependencies you’ll need for this chapter.
raywenderlich.com 368
Dagger by Tutorials Chapter 15: Dagger & Modularization
Speaking of reusability, this module already contains the definition of the different
@Scopes you created in the previous chapters. As you see in Figure 15.2, you have:
• ApplicationScope
• ActivityScope
• FragmentScope
This module is very simple. It only depends on the javax.inject library, as you see by
opening its build.gradle:
plugins {
id 'kotlin'
}
apply from: '../../../versions.gradle'
dependencies {
api "javax.inject:javax.inject:$javax_annotation_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:
$kotlin_version"
}
Encapsulating also means hiding the implementation details and exposing only the
main abstractions that form what you call public APIs. This is part of what you
learned in the very first chapter of this book: Encapsulation, or hiding details, is a
way to reduce or avoid dependency.
raywenderlich.com 369
Dagger by Tutorials Chapter 15: Dagger & Modularization
• Destination.kt file.
• Navigator interface.
• NavigatorImpl class.
In Destination.kt, you just have the Destination sealed class with its
implementations. These should stay public because they’re just data classes that
encapsulate values, not behavior.
// HERE
internal class NavigatorImpl(private val activity: Activity) :
Navigator {
override fun navigateTo(destination: Destination, params:
Bundle?) {
// ...
}
}
raywenderlich.com 370
Dagger by Tutorials Chapter 15: Dagger & Modularization
Build the app now and you’ll get the following error:
@Module(includes = [ActivityModule.Bindings::class])
class ActivityModule {
// ...
@Provides
@ActivityScope
fun provideNavigator(activity: Activity): Navigator =
NavigatorImpl(activity) // HERE
}
@Module
object NavigationModule {
@Provides
@ActivityScope
fun provideNavigator(activity: Activity): Navigator =
NavigatorImpl(activity)
}
Here, you moved the @Provides definition for Navigator from ActivityModule.kt
in the app module to NavigationModule.kt in libs.ui.navigation. Now, you can
access NavigatorImpl from a place where it’s visible.
The NavigationModule definition needs the dependencies from the Dagger libraries.
You also need to install the kapt plugin in build.gradle for the module. These
dependencies have been already set up for you in the starter project.
raywenderlich.com 371
Dagger by Tutorials Chapter 15: Dagger & Modularization
@Module(
includes = [
NavigationModule::class // 1
]
)
interface ActivityModule { // 2
@Binds
fun bindSplashPresenter(impl: SplashPresenterImpl):
SplashPresenter
@Binds
fun bindSplashViewBinder(impl: SplashViewBinderImpl):
SplashViewBinder
@Binds
fun bindMainPresenter(impl: MainPresenterImpl): MainPresenter
}
2. Convert the existing class into an interface containing all the existing @Binds
definitions.
raywenderlich.com 372
Dagger by Tutorials Chapter 15: Dagger & Modularization
This means you can eventually use a different Navigator implementation without a
single change in the main app.
If you have small modules, you also have small compilation units, which means
shorter building times. For example, suppose you have a monolithic app that
contains 100 classes you can usually build in 100 seconds. If you change a class, you
need to rebuild 100 classes and their related resources, which will take about the
same time: 100 secs.
Gradle is smart enough to avoid repeating some of the tasks in the building process.
Nevertheless, some of the tasks, like compile and assemble, need to be done.
Suppose now that you have 100 classes, but split them into five modules with 20
classes each. If you change a class in one module, you only need to compile that
module and its 20 classes.
The building time won’t be 1/5 of the original, because using modules introduces
some overhead, but large apps will show noticeable improvement.
Now, in the previous libs.ui.navigation example, you won’t need to build the
module anymore unless you change some of the files in it.
Creating libraries
Right now, the libs.ui.navigation module is in the same Busso project. However, you
could also publish it to an external repository and just leave a dependency to it in
the build.gradle of the main app. In that case, it would be like any other external
library you use, including Retrofit or Dagger itself. Having all classes and resources
in the same app module increases build times and makes code harder to manage.
raywenderlich.com 373
Dagger by Tutorials Chapter 15: Dagger & Modularization
• Want to make the library open source and allow other people to contribute.
As with many decisions, it isn’t always black or white. For instance, if you publish
your library to a private repository and need to change the code frequently, you can
just depend on the SNAPSHOT of the library. Or you might use it as a local module at
an early stage of your project, then open-source it after publishing it to an external
repository.
In any case, you should consider this question during your development process.
Ownership management
Suppose you contribute to an app with many features, and each feature has a
different team responsible for developing and maintaining its code. Then, imagine if
all the code was in the main app module.
The developers from the different teams would all work on the same codebase. This
is possible, but you could end up with severe problems because, in big companies,
you have hundreds of developers working on the same app. For instance, you’ll likely
have a lot of conflicts in your pull requests because there are no formal boundaries
between the code of the different features.
One solution is to split the work on a package basis, but this only works well in small
apps. For a big project, it wouldn’t make any difference.
Giving ownership of one or more modules to each team is a better way to manage the
code and its lifecycle, avoiding conflicts and creating physical boundaries in the
codebase.
raywenderlich.com 374
Dagger by Tutorials Chapter 15: Dagger & Modularization
Some modules are already available in the starter project and you’ll add code to
them, following along with the progression. You’ll work on different modules, in
particular on:
• networking
• location
• plugins
• Specific to Busso.
As a rule, you should keep anything that depends on the specific application in the
app and move what you can reuse to a different module.
@Module
class NetworkModule {
@Provides
@ApplicationScope
fun provideCache(application: Application): Cache = // 1
Cache(application.cacheDir, 100 * 1024L)// 100K
@Provides
@ApplicationScope
fun provideHttpClient(cache: Cache): OkHttpClient = // 2
// ...
@Provides
@ApplicationScope
fun provideRetrofit(httpClient: OkHttpClient): Retrofit = // 3
raywenderlich.com 375
Dagger by Tutorials Chapter 15: Dagger & Modularization
// ...
@Provides
@ApplicationScope
fun provideBussoEndPoint(retrofit: Retrofit): BussoEndpoint
{ // 4
return retrofit.create(BussoEndpoint::class.java)
}
}
1. Cache
2. OkHttpClient
3. Retrofit
4. BussoEndpoint
Points 1, 2 and 3 relate to reusable objects and 4 is specific to Busso. You’d like to
make the reusable code configurable from the main app. How can you do that?
In the starter project, you already have an empty libs.networking module available
that includes build.gradle with all the dependencies you need.
Note: In the code for the Busso App, you’ll see some .keep files that are there
to force Git to keep the empty folders in the repository. You can delete these
files when the related folder is no longer empty.
3. Add the dependency to the networking module in the build.gradle of the main
app module.
These are a bunch of steps, but they’re easy enough to code along with.
raywenderlich.com 376
Dagger by Tutorials Chapter 15: Dagger & Modularization
interface NetworkingConfiguration {
val cacheSize: Long // 1
val serverBaseUrl: String // 2
val dateFormat: String // 3
}
Here, you define an interface that requires you to implement properties referring to
the:
The module doesn’t know this information yet; the main app module should provide
it. You also need to use this information in the new @Module, which you’ll create in
the next step.
@Module
object NetworkingModule {
@Provides
@ApplicationScope
fun provideCache(
networkingConfiguration: NetworkingConfiguration, // 1
application: Application
): Cache =
Cache(
application.cacheDir,
networkingConfiguration.cacheSize // 1
)
raywenderlich.com 377
Dagger by Tutorials Chapter 15: Dagger & Modularization
@Provides
@ApplicationScope
fun provideHttpClient(cache: Cache): OkHttpClient =
OkHttpClient.Builder()
.cache(cache)
.build()
@Provides
@ApplicationScope
fun provideRetrofit(
networkingConfiguration: NetworkingConfiguration, // 2
httpClient: OkHttpClient
): Retrofit =
Retrofit.Builder()
.baseUrl(networkingConfiguration.serverBaseUrl) // 2
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(
GsonConverterFactory.create(
GsonBuilder()
.setDateFormat(networkingConfiguration.dateFormat) /
/ 2
.create()
)
)
.client(httpClient)
.build()
}
// ...
dependencies {
raywenderlich.com 378
Dagger by Tutorials Chapter 15: Dagger & Modularization
// ...
// Network Apis TO_BE_REMOVED // 1
// implementation "com.squareup.retrofit2:retrofit:
$retrofit_version"
// implementation "com.squareup.retrofit2:converter-gson:
$retrofit_gson_converter_version"
// implementation "com.squareup.retrofit2:adapter-
rxjava2:$retrofit_rx2_adapter_version"
// Networking library
implementation project(path: ':libs:networking') // 2
// ...
}
Here, you:
2. Add the dependency to the networking module, which will also provide the
dependencies you removed previously as transitive dependencies.
You’ve now completed the networking module — it’s time to work on the app.
raywenderlich.com 379
Dagger by Tutorials Chapter 15: Dagger & Modularization
But how do you tell Dagger to use this object as the implementation of
NetworkingConfiguration to add to the dependency graph for the
@ApplicationScope? You’ll do that in the next step.
Open ApplicationComponent.kt in the di package of the app module and apply the
following changes:
@Component(
dependencies = [NetworkingConfiguration::class], // 1
modules = [
ApplicationModule::class,
InformationPluginModule.ApplicationBindings::class,
InformationSpecsModule::class
]
)
@ApplicationScope
interface ApplicationComponent {
// ...
@Component.Factory
interface Factory {
fun create(
@BindsInstance application: Application,
networkingConfiguration: NetworkingConfiguration // 2
): ApplicationComponent
}
}
raywenderlich.com 380
Dagger by Tutorials Chapter 15: Dagger & Modularization
Changing Factory means you need to make a change when you create the instance
of ApplicationComponent. To do this, open Main.kt in the main package of the app
module and apply the following change:
You’re almost there! Now, you just need to remove the code you no longer need from
NetworkModule.kt.
Updating NetworkModule.kt
Open NetworkModule.kt in the network package of the app module and change it
to this:
@Module(
includes = [
NetworkingModule::class // HERE
]
)
object NetworkModule {
@Provides
@ApplicationScope
fun provideBussoEndPoint(retrofit: Retrofit): BussoEndpoint {
return retrofit.create(BussoEndpoint::class.java)
}
}
raywenderlich.com 381
Dagger by Tutorials Chapter 15: Dagger & Modularization
Here, you only add the NetworkingModule in includes and remove the @Provides
definitions that are now in the networking module. This @Module is now an object
and not a simple class.
Now, you can finally build and successfully run the app!
Following the modularization process for the Busso App, you wanted to move the
reusable code for networking into a different networking module. In particular, you
wanted to move the code that defines Cache, HttpClient and Retrofit. You created
NetworkingModule to hold this code.
raywenderlich.com 382
Dagger by Tutorials Chapter 15: Dagger & Modularization
The problem was that these objects require information that’s specific to a particular
app, like the base URL of the connection or the format to use when parsing dates.
The networking module cannot depend on the app module. The latter already
depends on the former, so it would give you a circular dependency, as shown in
Figure 15.5.
The magic happens because the app and the networking modules both contribute
to the dependency graph for @ApplicationScope, which you define with
ApplicationComponent.
Now, all the objects that both the app and the networking modules need are in the
dependency graph for @ApplicationScope.
raywenderlich.com 383
Dagger by Tutorials Chapter 15: Dagger & Modularization
3. Encapsulate GeoLocationPermissionCheckerImpl as a
GeoLocationPermissionChecker implementation, making it internal.
4. Fix the imports in the app module due to some changes in the destination
package. This should happen in ApplicationModule.kt in the app module.
After you’ve done these steps, the libs.location.rx source structure should look like
the one in Figure 15.6:
raywenderlich.com 384
Dagger by Tutorials Chapter 15: Dagger & Modularization
To understand the new structure, look at the dependency diagram in Figure 15.7:
raywenderlich.com 385
Dagger by Tutorials Chapter 15: Dagger & Modularization
1. The main app module depends on the weather and whereami modules. You
install the different plugins by adding their module in
InformationSpecsModule.kt in the plugins package of the app module.
4. The engine depends on other modules, like mpv, location and networking.
The weather and whereami modules are an example of how external modules
contribute to Set or Map when using multibinding.
After looking at the code available in the final project for this chapter, you can
successfully build and run the modularized version of the Busso App, getting what
you see in Figure 15.8:
raywenderlich.com 386
Dagger by Tutorials Chapter 15: Dagger & Modularization
Key points
• Modularization is a fundamental step in the development process of a project.
• Splitting your code into external modules can improve the build time of your app
and make your code more reusable.
• You set a simple Kotlin interface as the dependency for a @Component and use it to
pass information from the main app to a dependent module.
raywenderlich.com 387
Section V: Introducing Hilt
In the last section, you’ll learn everything you need to know about Hilt. Hilt is a
dependency injection library for Android that reduces the boilerplate of doing
manual dependency injection in your project.
Hilt is built on top of the DI library Dagger to benefit from the compile-time
correctness, runtime performance, scalability, and Android Studio support that
Dagger provides.
raywenderlich.com 388
16 Chapter 16: Dagger &
Android
By Massimo Carli
In the previous chapters of this book, you became a Dagger guru. You learned many
new concepts, in particular:
• How Dagger works and how it helps you implement the main principles of object-
oriented programming in your app.
• The different types of injection, how to know which one to use for a specific use
case and how to implement them with Dagger.
• How to use custom @Scopes to optimize the way your app uses resources.
• How to create a structure for your app’s code by splitting it into different modules,
resulting in improved build times, reusability and extensibility.
You’ve done a great job. Dependency injection is a tough topic and Google is working
to make it easier, improving the learning curve that, before this book, was very steep.
The result of Google’s effort is Hilt.
You’ll learn about Hilt in the remaining chapters of this book. Before you get to that,
though, take a moment to consider the legacy code you might need to maintain. To
handle that, you’ll learn about Dagger Android in this chapter.
raywenderlich.com 389
Dagger by Tutorials Chapter 16: Dagger & Android
There’s still a lot of code out there that uses Dagger Android, which is a library that
Google created with the goal of simplifying Dagger in Android apps. Unfortunately,
the result was not very successful and the solution is sometimes more complicated
than the problem it was supposed to solve.
For this reason, Google stopped developing new features for the Dagger Android
library in favor of Hilt. As you’ll learn in this chapter, Dagger Android helps reduce
the lines of code you need to write to configure Dagger, but it also has some
important limitations. You’ll get to know about these limitations as you continue to
refactor the Busso App.
As you know, Android is a container that manages the lifecycle of its standard
components, like Activitys, Services, ContentProviders and
BroadcastReceivers. Because of that, Dagger can’t create an instance of a standard
component like, for example, an Activity. That falls under the Android
environment’s responsibilities.
You cannot use constructor injection in Android. Instead, you must write code like
what you implemented in BusStopFragment.kt in the ui.view.busstop package of
the app module:
raywenderlich.com 390
Dagger by Tutorials Chapter 16: Dagger & Android
.inject(this)
super.onAttach(context)
}
// ...
}
You had to write this code in all the Activitys and Fragments of the app. You did
this exactly four times in Busso, and a larger project could need it even more often.
Sometimes, the code you need to write becomes very verbose, like what you have in
SplashActivity.kt in the ui.view.splash package in app.
In a perfect world, injection should happen from the outside. In the previous
example, SplashActivity should know nothing about the ApplicationComponent
you use for the actual injection and it shouldn’t have any code with inject().
But, can you completely remove all that code? The answer is: no. What you can do is
to make that code easier to write or, even better, ask Dagger to write it for you. That’s
exactly what Dagger Android does.
Dagger Android gives you a way to generate the code that, in the first part of this
book, you put in Injector implementations. To do this, you need some libraries and,
of course, an annotation processor. That’s Dagger Android!
// Dagger Android
implementation "com.google.dagger:dagger-android:
raywenderlich.com 391
Dagger by Tutorials Chapter 16: Dagger & Android
$dagger_version" // 1
implementation "com.google.dagger:dagger-android-support:
$dagger_version" // 2
kapt "com.google.dagger:dagger-android-processor:
$dagger_version" // 3
1. The Dagger Android library differs from the Dagger library you’ve used so far,
meaning you need to add new dependencies to the app.
2. If your app uses the Android support library, Dagger Android needs some more
classes, which you find in dagger-android-support.
3. You also need an annotation processor. This tells you that Dagger Android will
generate some code for you.
Note that the Dagger Android library’s version is the same as Dagger’s version.
That’s because, when Google releases a new version of Dagger, it also releases a new
version of the Android library as part of the same codebase.
As an example of how to use the Dagger Android library, you’ll refactor the Busso
App. In particular, you’ll migrate:
1. SplashActivity
2. MainActivity
3. BusStopFragment
4. BusArrivalFragment
Throughout this chapter, you’ll refer to the specific standard components or
Fragments as the injection target.
During the refactoring process, you’ll realize that Dagger Android has some
advantages — but at the cost of less freedom in how you structure the code.
raywenderlich.com 392
Dagger by Tutorials Chapter 16: Dagger & Android
3. Simplify the code you use in the injection targets for the actual injection.
Don’t worry if these tasks look complicated — everything will be clearer when you
code along. Executing each of these steps in order will help you understand how
Dagger Android works.
In this chapter, you’ll need to be very patient — the app won’t build successfully
until you’ve finished refactoring. As you learned in the previous chapters, it’s good
to try to build the app after each step anyway, to let Dagger generate whatever code
it can.
interface Injector<A> {
fun inject(target: A)
Dagger Android does a similar thing with the definition of this interface in
dagger.android:
interface AndroidInjector<T> { // 1
// 3
interface Factory<T> {
// 4
fun create(@BindsInstance instance: T): AndroidInjector<T>
}
}
raywenderlich.com 393
Dagger by Tutorials Chapter 16: Dagger & Android
Note: The Dagger Android library is in Java, but you see the Kotlin equivalent
here.
The last point is the reason for one of Dagger Android’s limitations. If you want to
create an AndroidInjector<T> through AndroidInjector.Factory<T>, you can
only pass a single object that must be of the same type as the injection target.
OK, but what’s responsible for creating the AndroidInjector<T> for your injection
target? In Busso, for instance, what generates the
AndroidInjector<MainActivity> and AndroidInjector<Splashctivity>
implementations?
raywenderlich.com 394
Dagger by Tutorials Chapter 16: Dagger & Android
In your case, you need to do this for MainActivity and SplashActivity. But how?
Well, you just need to remember what you already did without the Android Dagger
library — specifically, where you put the inject() functions earlier.
Refactoring MainActivity
Open ActivityComponent.kt in the di package of app and you’ll see the following:
@Subcomponent(
modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {
@Subcomponent.Builder
interface Builder {
fun activity(
@BindsInstance activity: Activity // HERE
): Builder
Of course, one is a Builder<T> and the other a Factory<T>, but the difference is
insignificant. They’re both creational patterns. Creational patterns provide various
object creation mechanisms, which increase flexibility and reuse of existing code.
This also tells you that the AndroidInjector<T> you’re asking Android Dagger to
generate for you will replace the existing ActivityComponent.
Now that you understand the background, it’s time to write some code.
Create a new di.activities.main package in the app module and create a new file in
it named MainActivitySubcomponent.kt, then ad the following code:
@Subcomponent(
modules = [
raywenderlich.com 395
Dagger by Tutorials Chapter 16: Dagger & Android
ActivityModule::class, // 1
]
)
@ActivityScope // 2
interface MainActivitySubcomponent :
AndroidInjector<MainActivity> { // 3
@Subcomponent.Factory // 4
interface Factory : AndroidInjector.Factory<MainActivity> // 5
}
4. The MainActivity object is something you provide when you need an instance
of the MainActivitySubcomponent implementation. This is why you define
@Subcomponent.Factory.
With this code, you tell Android Dagger that you need an injector for MainActivity.
Refactoring SplashActivity
Next, you’ll do the same for SplashActivity. Just create a new package named
splash in the existing di.activities and create SplashActivitySubcomponent.kt
inside. Then, add the following code:
@Subcomponent(
modules = [
ActivityModule::class,
]
)
@ActivityScope
interface SplashActivitySubcomponent :
AndroidInjector<SplashActivity> {
raywenderlich.com 396
Dagger by Tutorials Chapter 16: Dagger & Android
@Subcomponent.Factory
interface Factory : AndroidInjector.Factory<SplashActivity>
}
As you see, the code is basically the same as what you wrote for MainActivity, but
with a different generic type parameter value.
Now, you told Dagger which injectors you need for your Activitys. Before going
further, take a moment to see how to use them.
To see this in action, open MainActivity.kt in the ui.view.main package and replace
the existing code with:
@Inject
lateinit var mainPresenter: MainPresenter
raywenderlich.com 397
Dagger by Tutorials Chapter 16: Dagger & Android
Compare this code with what you had before and you’ll see:
• The injection code is now a simple invocation of the static method, inject(), on
an AndroidInjection, which passes the reference to MainActivity.
• Because of the previous point, you also removed the activityComp extension
property
// ...
}
Think about all the code you had to write before — and now, there’s just a single
instruction for every injection target.
raywenderlich.com 398
Dagger by Tutorials Chapter 16: Dagger & Android
1. inject() has a parameter of type Activity that is a common abstraction for all
the Activitys. Therefore, it includes both MainActivity and SplashActivity.
2. You use the Application object that must implement the HasAndroidInjector
interface, which defines a single androidInjector() operation. In short, a
HasAndroidInjector is any object that can provide an AndroidInjector<Any>.
This tells you that, to make everything work, your Application needs to be a
HasAndroidInjector and must then provide an implementation of
AndroidInjector<T>. Otherwise, you’ll get a RuntimeException.
The last point tells you what the next two steps you need to do are:
Next, you’ll start with Busso’s Application, which you extended in Main.kt.
To do this, open Main.kt in the main package for the app and change it like this:
@Inject
lateinit var dispatchingAndroidInjector:
DispatchingAndroidInjector<Any> // 2
raywenderlich.com 399
Dagger by Tutorials Chapter 16: Dagger & Android
In this code:
You also removed the reference to the ApplicationComponent and the appComp
extension property, which you don’t need anymore.
@Module(
includes = [
LocationModule::class,
NetworkModule::class,
AndroidSupportInjectionModule::class // HERE
]
)
object ApplicationModule
raywenderlich.com 400
Dagger by Tutorials Chapter 16: Dagger & Android
@ApplicationScope
interface ApplicationComponent {
You don’t need this anymore because the ActivityComponent is now Dagger
Android’s responsibility.
As a quick status update, clean up all the imports to the definitions you deleted then
build the app. You’ll only have some errors in the Fragments, which you’ll fix later.
@Module(
subcomponents = [MainActivitySubcomponent::class] // 1
)
interface MainActivityModule {
@Binds
@IntoMap
@ClassKey(MainActivity::class) // 2
fun bindMainActivitySubcomponentFactory(
factory: MainActivitySubcomponent.Factory // 3
): AndroidInjector.Factory<*>
}
raywenderlich.com 401
Dagger by Tutorials Chapter 16: Dagger & Android
2. Using multibinding, you add a new entry to a Map that has Class<T> as its key
and AndroidInjector.Factory<*> as a value. In this specific case, you assign
MainActivitySubcomponent.Factory as a value to the MainActivity::class
key.
@Module(
subcomponents = [SplashActivitySubcomponent::class]
)
interface SplashActivityModule {
@Binds
@IntoMap
@ClassKey(SplashActivity::class)
fun bindSplashActivitySubcomponentFactory(
factory: SplashActivitySubcomponent.Factory
): AndroidInjector.Factory<*>
}
@Component(
dependencies = [NetworkingConfiguration::class],
modules = [
ApplicationModule::class,
InformationPluginEngineModule::class,
InformationSpecsModule::class,
MainActivityModule::class, // HERE
SplashActivityModule::class // HERE
]
)
@ApplicationScope
raywenderlich.com 402
Dagger by Tutorials Chapter 16: Dagger & Android
interface ApplicationComponent {
// ...
}
Build the app now and you’ll get an unexpected error with the following message:
com.raywenderlich.android.ui.navigation.di.NavigationModule.prov
ideNavigator(activity)
What’s happening? Why is the compiler complaining about the Navigator interface?
This is related to the limitation you read about earlier. You’ll fix it next.
Dagger is picky with types, and you learned that even if MainActivity IS-A
Activity, Dagger treats them as different types unless you use @Binds to make
them equivalent.
However, you can’t just connect the MainActivity to Activity with @Binds
because sometimes, you need SplashActivity instead. One possible solution is to
use qualifiers to indicate when you should use which activity.
Using qualifiers
Start by creating a new file named NavigatorModule.kt in the new di.navigator
and add the following to it:
@Module
object NavigatorModule {
raywenderlich.com 403
Dagger by Tutorials Chapter 16: Dagger & Android
@Provides
@ActivityScope
@Named("Main") // HERE
fun providesMainActivityNavigator(owner: MainActivity):
Navigator =
NavigatorImpl(owner)
@Provides
@ActivityScope
@Named("Splash") // HERE
fun providesSplashActivityNavigator(owner: SplashActivity):
Navigator =
NavigatorImpl(owner)
}
Here, you’re saying that the Activity the Navigator needs can be MainActivity or
SplashActivity, depending on the qualifier you use when you declare the
dependency.
After this change, you can compile NavigatorModule — but you still need to apply
some changes.
raywenderlich.com 404
Dagger by Tutorials Chapter 16: Dagger & Android
Adding Qualifiers
First, open ActivityModule.kt in di.activities in app and replace the existing
NavigationModule with NavigatorModule, like this:
@Module(
includes = [
NavigatorModule::class // HERE
]
)
interface ActivityModule {
// ...
}
Then, you need to use the right qualifier in the right place. Open
SplashViewBinderImpl.kt in ui.view.splash and add @Named("Splash").
This means that the Navigator you use in SplashViewBinderImpl is the one using
the SplashActivity.
raywenderlich.com 405
Dagger by Tutorials Chapter 16: Dagger & Android
There’s one final place where you need to apply a fix. Open
BusStopListPresenterImpl.kt in ui.view.busstop and make the following change:
@FragmentScope
class BusStopListPresenterImpl @Inject constructor(
@Named("Main") private val navigator: Navigator, // HERE
private val locationObservable: Observable<LocationEvent>,
private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusStopListViewBinder>(),
BusStopListPresenter {
// ...
}
Build and run now and you’ll only get the errors about Fragments — which you’re
about to address.
That’s a lot. The good news is, you don’t have to learn anything new for Fragments.
You just need to follow the same process.
raywenderlich.com 406
Dagger by Tutorials Chapter 16: Dagger & Android
Injecting Fragments
Busso doesn’t build successfully yet because you still need to fix the Fragments for
Dagger Android. The process is basically the same as you followed for the Activitys,
but with some small but important differences.
@Subcomponent(
modules = [
FragmentModule::class
]
)
@FragmentScope
interface BusStopFragmentSubcomponent :
AndroidInjector<BusStopFragment> {
@Subcomponent.Factory
interface Factory : AndroidInjector.Factory<BusStopFragment> {
The only difference is that you override create() to make the return type,
BusStopFragmentSubcomponent, explicit.
In the same package, create a new file named BusStopFragmentModule.kt with the
following code:
@Module(
subcomponents = [BusStopFragmentSubcomponent::class]
)
interface BusStopFragmentModule {
raywenderlich.com 407
Dagger by Tutorials Chapter 16: Dagger & Android
@Binds
@IntoMap
@ClassKey(BusStopFragment::class)
fun bindBusStopFragmentSubcomponentFactory(
factory: BusStopFragmentSubcomponent.Factory
): AndroidInjector.Factory<*>
}
@Subcomponent(
modules = [
FragmentModule::class
]
)
@FragmentScope
interface BusArrivalFragmentSubcomponent :
AndroidInjector<BusArrivalFragment> {
@Subcomponent.Factory
interface Factory :
AndroidInjector.Factory<BusArrivalFragment> {
In the same package, also create BusArrivalFragmentModule.kt and add this code:
@Module(
subcomponents = [BusArrivalFragmentSubcomponent::class]
)
interface BusArrivalFragmentModule {
@Binds
@IntoMap
@ClassKey(BusArrivalFragment::class)
fun bindBusArrivalFragmentSubcomponentFactory(
factory: BusArrivalFragmentSubcomponent.Factory
): AndroidInjector.Factory<*>
}
raywenderlich.com 408
Dagger by Tutorials Chapter 16: Dagger & Android
@Module(includes =
[InformationPluginEngineModule.FragmentBindings::class]) // HERE
interface FragmentModule {
// ...
}
Now that you’ve told Dagger which AndroidInjector<T> to create for Busso’s
Fragments, the next step is to simply the injection code.
Note here how you used inject() for AndroidSupportInjection. That’s because
you’re using the support library for Fragments and the parameter for inject() must
be of the right type.
inject() works the same way as its counterpart for Activitys does, so the next
step is to make MainActivity an object of type HasAndroidInjector — just as you
did for Main.
raywenderlich.com 409
Dagger by Tutorials Chapter 16: Dagger & Android
@Inject
lateinit var mainPresenter: MainPresenter
@Inject
lateinit var androidInjector:
DispatchingAndroidInjector<Any> // 2
1. Implement HasAndroidInjector.
You’re almost done. The last step is to configure the relationship between the
@Subcomponents for the Fragments and those of the Activitys.
raywenderlich.com 410
Dagger by Tutorials Chapter 16: Dagger & Android
@Subcomponent(
modules = [
ActivityModule::class,
BusStopFragmentModule::class, // HERE
BusArrivalFragmentModule::class // HERE
]
)
@ActivityScope
interface MainActivitySubcomponent :
AndroidInjector<MainActivity> {
@Subcomponent.Factory
interface Factory : AndroidInjector.Factory<MainActivity>
}
You did it! You can finally build and run successfully, getting what’s in Figure 16.1:
raywenderlich.com 411
Dagger by Tutorials Chapter 16: Dagger & Android
To improve this, Dagger Android provides some utility classes that, in most cases,
help you write less code. In particular, Dagger Android provides:
• DaggerApplication
• DaggerAppCompatActivity
• @ContributesAndroidInjector
Next, you’ll see the benefits of these tools.
Using DaggerApplication
Main, which is the Application for Busso, has quite a defined structure. It has to:
1. Implement HasAndroidInjector.
Try this out by opening Main.kt and applying the following changes:
raywenderlich.com 412
Dagger by Tutorials Chapter 16: Dagger & Android
// ...
@ApplicationScope
interface ApplicationComponent : AndroidInjector<Main> { // 1
@Component.Factory
interface Factory { // 2
fun create(
@BindsInstance application: Application,
networkingConfiguration: NetworkingConfiguration
): ApplicationComponent
}
}
Now, build and run and check that everything works as expected.
Using DaggerAppCompatActivity
Android Dagger provides the tools to reduce the boilerplate for Activitys as well.
Open MainActivity.kt in ui.view.main and apply the following changes:
@Inject
lateinit var mainPresenter: MainPresenter
raywenderlich.com 413
Dagger by Tutorials Chapter 16: Dagger & Android
Of course, you might need some more work, in case you already have a hierarchy for
your app’s Activitys.
You’ve probably noticed how repetitive the code you wrote to create the
@Subcomponent and @Module for each AndroidInjector<T> is. The question now is:
Can you avoid that? In this case, the answer is yes.
@Module
interface ActivityBindingModule { // 1
@ContributesAndroidInjector( // 3
modules = [
ActivityModule::class,
BusStopFragmentModule::class,
BusArrivalFragmentModule::class
]
)
@ActivityScope // 4
fun mainActivity(): MainActivity // 2
@ContributesAndroidInjector( // 3
modules = [
ActivityModule::class
]
)
@ActivityScope
fun splashActivity(): SplashActivity // 2
}
raywenderlich.com 414
Dagger by Tutorials Chapter 16: Dagger & Android
2. Create an abstract function that has the injection target type as the return type.
In this case, mainActivity() returns MainActivity and splashActivity()
returns SplashActivity.
You no longer need the previous definitions, so you can delete the following files:
• MainActivityModule.kt
• MainActivitySubcomponent.kt
• SplashActivityModule.kt
• SplashActivitySubcomponent.kt
As your final step, you just need to replace the previous @Modules with the new one
in ApplicationComponent.kt, like this:
@Component(
dependencies = [NetworkingConfiguration::class],
modules = [
ApplicationModule::class,
InformationPluginEngineModule::class,
InformationSpecsModule::class,
ActivityBindingModule::class // HERE
]
)
@ApplicationScope
interface ApplicationComponent : AndroidInjector<Main> {
// ...
}
raywenderlich.com 415
Dagger by Tutorials Chapter 16: Dagger & Android
@Module
interface FragmentBindingModule {
@ContributesAndroidInjector(
modules = [
FragmentModule::class
]
)
@FragmentScope
fun busStopFragment(): BusStopFragment
@ContributesAndroidInjector(
modules = [
FragmentModule::class
]
)
@FragmentScope
fun busArrivalFragment(): BusArrivalFragment
}
This code has the same structure as ActivityBindingModule.kt except that it relates
to Fragments instead of Activitys.
Now, just as you did for the Activitys, you can delete the following files:
• BusStopFragmentModule.kt
• BusStopFragmentSubcomponent.kt
• BusArrivalFragmentModule.kt
• BusArrivalFragmentSubcomponent.kt
@Module
interface ActivityBindingModule {
@ContributesAndroidInjector(
modules = [
ActivityModule::class,
raywenderlich.com 416
Dagger by Tutorials Chapter 16: Dagger & Android
FragmentBindingModule::class // HERE
]
)
@ActivityScope
fun mainActivity(): MainActivity
// ...
}
Key points
• Android is responsible for the lifecycle of its standard components, like
Activitys, Services, BroadcastReceivers and ContentProviders. This
prevents you from using constructor injection.
• Dagger Android is Google’s first solution for reducing the boilerplate code you
need to write to inject into Android standard components. The next solution is
Hilt, which you’ll learn about in the rest of this book.
• Using Dagger Android requires a deep knowledge of Dagger and, specifically, how
@Subcomponents and multibinding work.
• Dagger Android provides some utility classes to reduce the code you need to write.
Wow! This has been intense, but you managed to learn everything you need about
Dagger Android. Now you’re ready for the next generation of dependency injection
in Android: Hilt!
raywenderlich.com 417
17 Chapter 17: Hilt — Dagger
Made Easy
By Massimo Carli
In the previous chapter, you learned how Dagger Android works and Android needs a
special library to use Dagger. You learned that Dagger can’t instantiate Android
standard components because their lifecycle is the responsibility of the Android
system, which works as a container. Dagger Android has not been very successful but
it taught Google lessons that they used when making architectural decisions for Hilt.
• Standardize the way developers create @Scopes and apply them to @Components.
You’ll learn everything you need to know about testing with Hilt in this chapter,
including:
Of course, you’ll put all these things to work in the Busso App.
raywenderlich.com 418
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
Note: At this time, Hilt is still in alpha release. Things might change in future
versions of the library.
5. Defined different @Modules to tell Dagger how to bind a specific class to a given
type.
To do this, you had to learn many different concepts and several ways to give Dagger
the information it needs. As you’ll learn in detail in this chapter, Hilt makes things
much easier by:
1. Providing a predefined set of @Scopes to standardize the way you bind an object
to a specific @Component’s lifecycle.
2. Doing the same for @Components. Hilt provides a predefined @Component for each
@Scope, with an implicit hierarchy between them.
raywenderlich.com 419
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
4. Defining a new way to bind a @Module to a specific @Component. Now you install
a @Module in one or more @Components.
This will become clearer when you start using Hilt for your Busso App.
Note: As you already know, refactoring with Dagger means that you usually
need to complete all the configurations before you can successfully build and
run the app. This is also true in this case. However, it’s good practice to try
building the app at each step, anyway, allowing Dagger to generate what it
can.
raywenderlich.com 420
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
Start by opening the Busso App project in the starter folder of the materials for this
chapter. This is the final project from the previous chapter, which uses Dagger
Android. When you open the project, you’ll have this structure:
raywenderlich.com 421
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
buildscript {
ext.kotlin_version = "1.4.20"
ext.hilt_version = "2.28-alpha" // 1
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:
$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:
$hilt_version" // 2
}
}
// ...
1. Include the definition of ext.hilt_version with the value of the latest version
of Hilt.
Now, you have to apply the Hilt plugin to build.gradle for the modules that need it.
For starters, open build.gradle from app, as in Figure 17.3:
plugins {
id 'com.android.application'
id 'kotlin-android'
id "kotlin-kapt"
id "dagger.hilt.android.plugin" // 1
raywenderlich.com 422
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
}
apply from: '../versions.gradle'
android {
// ...
compileOptions { // 2
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
// ...
}
dependencies {
// ...
// Hilt dependencies
implementation "com.google.dagger:hilt-android:
$hilt_version" // 3
kapt "com.google.dagger:hilt-android-compiler:
$hilt_version" // 4
// ...
}
There are some interesting things to note in the Gradle file above. Here, you:
1. Add the plugin for Hilt. The plugin isn’t necessary, and Hilt works without it, but
it makes the developer experience better. For instance, it executes some bytecode
transformations to make the code more IDE-friendly. This helps auto-complete
the code without messing with auto-generated code. It also makes some code
simpler. You’ll see this later, when you work with @HiltAndroidApp.
2. Specify using Java 1.8, which Hilt requires. Busso already has the proper
configuration in compileOptions.
4. Indicate the specific library kapt uses for Hilt’s annotation processor.
Now, sync the Busso project with the Gradle file and you’re ready to use Hilt in your
app. You just need a way to activate the Hilt plugin by providing an entry point to the
Hilt world. You’ll do this by using Application. In Busso, you’ll find this class in
Main.kt.
raywenderlich.com 423
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
Your next step in migrating Busso to Hilt is to define the entry point for the entire
dependency graph. You must do this in your app’s Application implementation. If
your app doesn’t have an Application implementation, you need to create one.
@HiltAndroidApp // 1
class Main : Application() // 2
Hey, where’s all the code? Don’t worry, one of the main benefits of Hilt is that you
don’t need all the previous code. Here, you:
1. Used @HiltAndroidApp to tell Dagger that this is the entry point for Busso’s
dependency graph.
DaggerApplicationComponent
.factory()
.create(this, BussoConfiguration)
• Application.
• NetworkingConfiguration.
raywenderlich.com 424
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
Adding a @Module
You already know how to solve the first point: you need a @Module. Open
ApplicationModule.kt in di and add the following definition:
@Module(
includes = [
LocationModule::class,
NetworkModule::class,
AndroidSupportInjectionModule::class
]
)
object ApplicationModule {
@Provides
fun provideNetworkingConfiguration(): NetworkingConfiguration
= // HERE
BussoConfiguration
}
Here, you tell Dagger that whenever it needs NetworkingConfiguration, it’ll use
BussoConfiguration.
raywenderlich.com 425
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
@Module(
includes = [
LocationModule::class,
NetworkModule::class,
AndroidSupportInjectionModule::class
]
)
@InstallIn(ApplicationComponent::class) // HERE
object ApplicationModule {
@Provides
fun provideNetworkingConfiguration(): NetworkingConfiguration
=
BussoConfiguration
}
You’ll see this in more detail later. At the moment, you’re using @InstallIn to tell
Dagger that you want all the bindings you have in ApplicationModule.kt to be a
part of the dependency graph for ApplicationComponent.
In this section, you did something very simple but powerful. You told Dagger:
1. To use Main as the entry point for Busso’s dependency graph. You did this using
@HiltAndroidApp.
3. That all the definitions you made in ApplicationModule will be part of the
dependency graph for the predefined ApplicationComponent. You did this using
@InstallIn, which you’ll learn more about later.
raywenderlich.com 426
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
You also learned that Hilt makes Application automatically available to the
dependency graph for the ApplicationComponent — which also makes it available
to all the other predefined Hilt @Components.
You’ll learn about all the available @Scopes and @Components later. At the moment,
it’s important to know that the @Scope for ApplicationComponent is @Singleton.
To migrate from the existing custom @Scopes in the libs.di.scopes module to Hilt’s,
make the following changes:
3. Fix the dependent modules using the Hilt @Scopes instead of the ones you just
deleted.
Note: If you don’t want to remove your custom @Scopes, Hilt provides you the
@AliasOf annotation, which allows you to use your custom @Scope annotation
in place of the ones Hilt provides. You’ll see an example of this later.
raywenderlich.com 427
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
plugins {
id 'com.android.library'
id 'kotlin-android'
id "kotlin-kapt"
id "dagger.hilt.android.plugin"
}
apply from: '../../../versions.gradle'
android {
compileSdkVersion compile_sdk_version
buildToolsVersion build_tool_version
}
dependencies {
api "javax.inject:javax.inject:$javax_annotation_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:
$kotlin_version"
api "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}
Of course, now you need to replace all the existing custom @Scopes with Hilt’s. But
don’t worry about that, yet. At the moment, just replace the previous
@ApplicationScope with @Singleton. You can find all the occurrences with a
simple search:
raywenderlich.com 428
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
@Module(
includes = [
NavigatorModule::class
]
)
@InstallIn(ActivityComponent::class) // HERE
interface ActivityModule {
// ...
}
Now, search for @ActivityScope and replace all the occurrences with
@ActivityScoped, as in Figure 17.5:
raywenderlich.com 429
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
After replacing those occurrences, there’s one more step to complete your work with
Activitys: You need to execute the injection in MainActivity and
SplashActivity.
Later, you’ll see a detailed list of all the classes Hilt’s @AndroidEntryPoint
annotation supports. At the moment, you just need to open SplashActivity.kt in
ui.view.splash and change it like this:
@AndroidEntryPoint // 1
class SplashActivity : AppCompatActivity() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
// AndroidInjection.inject(this) // 2 TO DELETE
super.onCreate(savedInstanceState)
makeFullScreen()
setContentView(R.layout.activity_splash)
splashViewBinder.init(this)
}
// ...
}
1. Add @AndroidEntryPoint.
In the same way, open MainActivity.kt in ui.view.main and apply the following
changes
@AndroidEntryPoint // 1
class MainActivity : AppCompatActivity() { // 2
// ...
}
1. Added @AndroidEntryPoint.
raywenderlich.com 430
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
Now, it’s time to migrate Busso’s Fragments to Hilt. By now, you probably know how
to do that. :]
1. @FragmentScoped
2. FragmentComponent
raywenderlich.com 431
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
@Module(includes =
[InformationPluginEngineModule.FragmentBindings::class])
@InstallIn(FragmentComponent::class) // HERE
interface FragmentModule {
// ...
}
@AndroidEntryPoint // 1
class BusStopFragment : Fragment() {
// ...
/* START REMOVE
override fun onAttach(context: Context) { // 2
AndroidSupportInjection.inject(this)
super.onAttach(context)
} END REMOVE
*/
// ...
}
2. Remove the existing inject() invocation. Hilt will inject the objects you require
into BusStopFragment for you.
@AndroidEntryPoint // 1
class BusArrivalFragment : Fragment() {
// ...
/* START REMOVE
override fun onAttach(context: Context) { // 2
AndroidSupportInjection.inject(this)
super.onAttach(context)
} END REMOVE
*/
// ...
}
raywenderlich.com 432
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
@Module(
includes = [
NetworkingModule::class
]
)
@InstallIn(ApplicationComponent::class) // HERE
object NetworkModule {
// ...
}
raywenderlich.com 433
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
@Module(
includes = [
LocationModule::class,
NetworkModule::class,
AndroidSupportInjectionModule::class,
InformationPluginEngineModule::class // HERE ADD
]
)
@InstallIn(ApplicationComponent::class)
object ApplicationModule {
// ...
}
@Module(
includes = [
WhereAmIModule::class,
WeatherModule::class
]
)
@InstallIn(ApplicationComponent::class) // HERE
object InformationSpecsModule
raywenderlich.com 434
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
@Module(
includes = [
NavigationModule::class // HERE
]
)
@InstallIn(ActivityComponent::class)
interface ActivityModule {
// ...
}
@FragmentScoped
class BusStopListPresenterImpl @Inject constructor(
private val navigator: Navigator, // HERE
private val locationObservable: Observable<LocationEvent>,
private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusStopListViewBinder>(),
BusStopListPresenter {
// ...
}
For your last step, restore the encapsulation for libs.ui.navigation using the
internal visibility modifier in the NavigatorImpl.kt file, like this:
// HERE
internal class NavigatorImpl(private val activity: Activity) :
Navigator {
// ...
}
raywenderlich.com 435
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
You did it! You can now successfully build and run Busso, ending up with what you
see in Figure 17.7:
• You use @InstallIn to make the bindings in a specific @Module available to one or
more @Components.
raywenderlich.com 436
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
You won’t believe it but these are the main things you need to know to use Hilt in
your app. Of course, you only saw a few of the @Components and @Scopes available.
Now, you’ll get a quick overview of everything Hilt provides.
You already know how to use them. In the following sections, you’ll just learn what’s
available.
• View
• Service
• BroadcastReceiver
You might notice that this list doesn’t match the Android standard components. It
adds Fragments and Views but ContentProvider is missing. You might wonder
when and how you could support more classes.
raywenderlich.com 437
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
In theory, you should be able to use the existing entry points, but sometimes you
need to handle third-party libraries or components that Hilt doesn’t support yet.
As you’ll learn in the next chapter, Hilt provides you with the APIs to create your
own entry point, component and scope and integrate them with the ones you already
have.
raywenderlich.com 438
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
In this case, you need to take care that you don’t have Activity as default binding,
but rather Application, as in ApplicationComponent.
Finally, here are the default bindings for each predefined @Component:
• ApplicationComponent: Application
• ActivityRetainedComponent: Application
When you need to decide which @Scope and @Component to use in your bindings, you
need to consider:
After that, you can apply the rules you just learned with the Busso App.
1. @AliasOf
2. @ApplicationContext
3. @ActivityContext
You’ll see a simple example for each of those next.
raywenderlich.com 439
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
Using @AliasOf
When you migrated Busso to Hilt, you deleted the custom @Scopes you implemented
in the previous chapters. You deleted @ApplicationScope, @ActivityScope and
@FragmentScope in favor of the predefined @Singleton, @ActivityScoped and
@FragmentScoped.
Suppose you don’t like @Singleton and want to keep the same naming convention
by using @ApplicationScoped, instead.
@Scope
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@AliasOf(Singleton::class) // HERE
annotation class ApplicationScoped
With this definition, you tell Dagger that @ApplicationScoped is just an alias for
@Singleton. To prove this, just search for @Singleton and replace all the instances
with the new @ApplicationScoped. After that, build and run the app as usual and it
will work.
Of course, the best use case for @AliasOf is when you already have a lot of
occurrences for a custom @Scope and you want to avoid replacing too much code.
Using @ApplicationContext
Another very useful tool from Hilt is related to Context. You’ve seen that some of
the predefined @Components have an Application for default bindings, while others
have an Activity. As you know, they’re both implementations of the Android
Context and if you want to distinguish one from the other, you use a qualifier. In
this specific case, however, Hilt already provides the qualifier you need.
@Module
class LocationModule {
@ApplicationScoped
@Provides
fun provideLocationManager(application: Application):
LocationManager = // HERE
application.getSystemService(Context.LOCATION_SERVICE) as
LocationManager
raywenderlich.com 440
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
// ...
}
@Module
class LocationModule {
@ApplicationScoped
@Provides
fun provideLocationManager(
@ApplicationContext context: Context // HERE
): LocationManager =
context.getSystemService(Context.LOCATION_SERVICE) as
LocationManager
// ...
}
Using @ActivityContext
You can do the same when you need the Context of an Activity. In that case, you
also have the opportunity to improve Busso’s resource management.
Open LocationModule.kt in libs.location.rx and replace the current code with the
following:
// 1
class LocationModule {
@Module
object ApplicationBindings { // 2
@ApplicationScoped
@Provides
fun provideLocationManager(
@ApplicationContext context: Context
): LocationManager =
context.getSystemService(Context.LOCATION_SERVICE) as
LocationManager
}
@Module
object ActivityBindings { // 3
@ActivityScoped // 4
@Provides
fun providePermissionChecker(
raywenderlich.com 441
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
@Provides
@ActivityScoped // 4
fun provideLocationObservable(
locationManager: LocationManager,
permissionChecker: GeoLocationPermissionChecker
): Observable<LocationEvent> =
provideRxLocationObservable(locationManager, permissionChecker)
}
}
1. LocationModule is not a @Module anymore — it’s just a container for a few other
@Modules for bindings with different scopes.
5. Use @ActivityContext to tell Dagger that the Context you require for
GeoLocationPermissionChecker is Activity.
@Module(
includes = [
LocationModule.ApplicationBindings::class, // HERE
NetworkModule::class,
AndroidSupportInjectionModule::class,
InformationPluginEngineModule::class
]
)
@InstallIn(ApplicationComponent::class)
object ApplicationModule {
// ...
}
raywenderlich.com 442
Dagger by Tutorials Chapter 17: Hilt — Dagger Made Easy
@Module(
includes = [
NavigationModule::class,
LocationModule.ActivityBindings::class // HERE
]
)
@InstallIn(ActivityComponent::class)
interface ActivityModule {
// ...
}
Now, you can build and run Busso as usual and verify it works.
Key points
• The main goal of Hilt is to make Dagger easier to learn and to use.
Congratulations! In this chapter, you learned the main concepts of Hilt, the new
framework with the goal of simplifying Dagger usage in Android apps. However, Hilt
is more than that. In the next chapter, you’ll learn more about it — in particular, how
to use customize it and how to use it with Android architecture components.
raywenderlich.com 443
18 Chapter 18: Hilt &
Architecture Components
By Massimo Carli
In the previous chapter, you learned how to migrate the Busso App from Dagger
Android to Hilt. You saw the main Hilt architectural decisions and how to apply them
using new APIs like @InstallIn and @AndroidEntryPoint.
In this chapter, you’ll learn more about Hilt, including how to:
• Use Hilt with other supported Android standard components, like Services.
Throughout the chapter, you’ll work with the RayTrack app. Don’t worry about the
amount of code the app has. It’s purposely a complex app, to give you the
opportunity to work with Hilt’s architectural components. You’ll only focus on the
things that have to do with Hilt. To save space, you’ll only see the most relevant
parts of the code in this chapter.
raywenderlich.com 444
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
• libs.di.scope
• libs.location.api
• libs.ui.navigation
• libs.location.api-android
• libs.location.flow
raywenderlich.com 445
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
Build and run and you’ll get, after the splash screen and permission request,
something like in Figure 18.2:
raywenderlich.com 446
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
Selecting the notification area returns you to the list of locations shown in Figure
18.2. Finally, you can select the Clear Data button and delete the database content
at any time.
Now that you know how RayTrack works, it’s time to see how it’s set up.
RayTrack’s architecture
RayTrack consists of three main parts:
This means that you have some specific constraints you must follow. For instance,
you must display a Notification when Service is running. In Figure 18.4, you can
see the architecture for this part of the RayTrack app:
raywenderlich.com 447
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
2. You start and stop TrackingService directly from MainActivity using the
classic startService() and stopService().
5. Tracker is the abstraction of the object responsible for the actual tracking of the
location.
Looking at the diagram in Figure 18.4, you also understand which objects
TrackingService depends on. It needs a reference to:
• Tracker
• TrackerStateManager
The good news is that you can use Service as an injection target for Hilt. To inject
the required dependencies, you just need to do what’s already in TrackingService.
Injecting dependencies
Open TrackingService.kt in raytracker.service and look at the following code:
@ExperimentalCoroutinesApi
@AndroidEntryPoint // 1
class TrackingService : LifecycleService() {
@Inject
lateinit var tracker: Tracker // 2
@Inject
lateinit var trackerStateManager: TrackerStateManager // 2
// ...
}
raywenderlich.com 448
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
2. @Inject for the property containing the reference to the dependent objects.
To understand where those dependencies come from, just click on the icons in Figure
18.5:
interface TrackerModule {
@Module(
includes = [FlowLocationModule::class]
)
@InstallIn(ServiceComponent::class) // HERE
interface ServiceBindings {
@Binds
fun bindTracker(
impl: TrackerImpl
): Tracker
}
// ...
}
This shows how to use @InstallIn to install the Tracker implementation in the
dependency graph for the ServiceComponent.
raywenderlich.com 449
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
@ServiceScoped // HERE
class TrackerImpl @Inject constructor(
private val trackerStateManager: TrackerStateManager,
private val locationFlow: @JvmSuppressWildcards
Flow<LocationEvent>
) : Tracker, CoroutineScope {
// ...
}
This is an example of @ServiceScoped as the @Scope for an object that lives as long
as a Service.
Select the icon next to TrackerStateManager and you end up, once again, in
TrackerModule.kt in di, where you have the following code:
interface TrackerModule {
// ...
@Module
@InstallIn(ApplicationComponent::class) // HERE
interface ApplicationBindings {
@Binds
fun bindTrackerStateManager(
impl: TrackerStateManagerImpl
): TrackerStateManager
}
}
@ApplicationScoped // HERE
class TrackerStateManagerImpl @Inject constructor(
@ApplicationContext private val context: Context
) : TrackerStateManager {
// ...
}
Here you just use @ApplicationScoped, which is the same @AliasOf for @Singleton
that you used in the previous chapters.
TrackingService is an example of how to use Hilt with a Service. But what about a
standard component that isn’t supported yet? You’ll see how to do that next.
raywenderlich.com 450
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
You still use @AndroidEntryPoint to tag the Service as an injection target and
@Inject to assign values to local properties. Service is an Android standard
component that Hilt supports — but you’re not always so lucky. At the moment, for
example, Hilt doesn’t support ContentProviders. However, Hilt gives you some
tools to fix the problem.
raywenderlich.com 451
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
raywenderlich.com 452
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
Note: In this case, you just need to inject TrackDatabase. This is a component
that lives as long as the app, so it’s in ApplicationComponent.
Adding bindings
To start, open TrackDBModule.kt in di and add:
@Module
@InstallIn(ApplicationComponent::class)
object TrackDBModule {
@Provides
fun provideTrackDatabase( // HERE
@ApplicationContext context: Context
): TrackDatabase =
Room.databaseBuilder(
context,
TrackDatabase::class.java,
Config.DB.DB_NAME
).build()
// ...
}
raywenderlich.com 453
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
Now, TrackDatabase is in the dependency graph for the app. You need to use it from
RayTrackContentProvider.
@EntryPoint // 1
@InstallIn(ApplicationComponent::class) // 2
interface ContentProviderEntryPoint {
1. @EntryPoint to tell Hilt, and then Dagger, that this is the interface you want
your custom component to use to access objects in the dependency graph.
3. trackDatabase() to tell Hilt, and then Dagger, that the object you need is
TrackDatabase.
Accessing TrackDatabase
Finally, you need to access TrackDatabase in RayTrackContentProvider. In
RayTrackContentProvider.kt in repository.contentprovider, replace the existing
getRoomDatabase() with:
raywenderlich.com 454
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
It’s important to note that EntryPointAccessors is a utility class that Hilt provides
for cases where the bindings you need are in @Components that Hilt already supports.
Other methods are:
• EntryPointAccessors.fromActivity()
• EntryPointAccessors.fromFragment()
• EntryPointAccessors.fromView()
In your case, you used EntryPointAccessors.fromApplication() because the
binding for TrackDatabase is in ApplicationComponent.
Note: To be picky, what you just did is not actually dependency injection. You
still depend on the EntryPointAccessors and the injection doesn’t come
from outside. This is reminiscent of what happens with the service locator
pattern.
Great job! You managed to create a custom @EntryPoint for a ContentProvider you
implemented in RayTrackContentProvider. Build and run and check that
everything works as expected.
raywenderlich.com 455
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
The code in MainActivity.kt in ui.main shows something that the diagram already
highlights:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var trackerStateManager: TrackerStateManager // 2
raywenderlich.com 456
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
)
createNotificationChannel()
startStopButton = findViewById(R.id.startStopTrackingButton)
trackDataRecyclerView =
findViewById(R.id.location_recyclerview)
initRecyclerView(trackDataRecyclerView)
handleButtonState(locationViewModel.locationEvents().value)
handleTrackDataList(locationViewModel.storedLocations().value)
}
// ...
Here, MainActivity:
Now, you can migrate and replace the composition relationship with a simple
dependency.
raywenderlich.com 457
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
// ...
dependencies {
// ...
// ViewModel Hilt support
implementation "androidx.hilt:hilt-lifecycle-viewmodel:
$viewmodel_hilt_version" // 1
kapt "androidx.hilt:hilt-compiler:$viewmodel_hilt_version" //
2
// ...
}
Now, sync Gradle for your project and you’re ready to use @ViewModelInject in your
code.
@ExperimentalCoroutinesApi
class CurrentLocationViewModel(
application: Application,
private val trackerStateManager: TrackerStateManager,
private val trackDataHelper: TrackDataHelper
) : AndroidViewModel(application) {
// ...
}
raywenderlich.com 458
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
@ExperimentalCoroutinesApi
class CurrentLocationViewModel @ViewModelInject constructor( //
HERE
application: Application,
private val trackerStateManager: TrackerStateManager,
private val trackDataHelper: TrackDataHelper
) : AndroidViewModel(application) {
// ...
}
Here, you just added @ViewModelInject to the primary constructor. In your case, all
the parameters are already in the dependency graph for the app so you won’t get any
complaints when you build.
@ExperimentalCoroutinesApi
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
//@Inject // REMOVE 1
//lateinit var trackerStateManager: TrackerStateManager
raywenderlich.com 459
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
initRecyclerView(trackDataRecyclerView)
handleButtonState(locationViewModel.locationEvents().value)
handleTrackDataList(locationViewModel.storedLocations().value)
}
// ...
}
Here, you:
Now, build and run to check that everything works. The diagram in Figure 18.8 gives
you a useful description of what you just did:
Great job! You just removed all the composition dependencies and replaced them
with loosely coupled dependencies.
raywenderlich.com 460
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
However, Hilt provides the APIs to create your own @Component with a specific
@Scope and its own lifecycle. It’s worth mentioning that this is not something you
should do very often because the existing @Components already cover most of the use
cases.
A possible example is the logged state for a user in an app. You might have objects
that need to be there only when the user is logged in to the app, and which you
should remove if the user isn’t logged in.
In RayTrack’s case, you’ll define a new @Component for objects that need to exist
only when the Tracker is running and there’s something to display. This means that
the @Component should exist only when there’s an Activity to display locations
from a running Tracker.
3. Add a @DefineComponent.Builder.
raywenderlich.com 461
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
@Scope
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class TrackRunningScoped
This is nothing different from what you did for other custom @Scopes in the previous
chapters.
@DefineComponent(parent = ActivityComponent::class) // 1
@TrackRunningScoped // 2
interface TrackRunningComponent
In these few lines, there are some important things to note. Here, you use:
raywenderlich.com 462
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
Adding a @DefineComponent.Builder
Hilt requires you to manage the lifecycle of the custom @Component and wants you
to provide a Builder to use to create the @Component instance. To do this, open
TrackRunningComponent.kt and add the following code:
@DefineComponent(parent = ActivityComponent::class)
@TrackRunningScoped
interface TrackRunningComponent {
@DefineComponent.Builder // 1
interface Builder {
fun sessionId(@BindsInstance sessionId: Long): Builder // 2
fun build(): TrackRunningComponent // 3
}
}
raywenderlich.com 463
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
Now, you’ve created the TrackRunningComponent custom @Component with its own
@TrackRunningScoped. For your next step, you need a way to manage its lifecycle.
@ActivityScoped // 1
class TrackRunningComponentManager @Inject constructor(
private val trackRunnningBuilder:
TrackRunningComponent.Builder // 2
) {
fun stop() {
if (trackRunningComponent != null) {
trackRunningComponent = null // 5
}
}
}
raywenderlich.com 464
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
@ExperimentalCoroutinesApi
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
// ...
@Inject
lateinit var trackRunningComponentManager:
TrackRunningComponentManager // 1
trackRunningComponentManager.startWith(System.currentTimeMillis(
)) // 2
text = getString(R.string.stop_tracking)
setOnClickListener {
stopService(Intent(this@MainActivity,
TrackingService::class.java))
}
} else {
trackRunningComponentManager.stop() // 3
text = getString(R.string.start_tracking)
setOnClickListener {
startService(Intent(this@MainActivity,
TrackingService::class.java))
}
raywenderlich.com 465
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
}
}
}
Now, TrackRunningComponent is there only when you actually need it and removed
when you don’t. You created a place with a specific lifecycle where you might want to
put objects you’ll use only when Track is running and there’s an Activity to display
the locations.
Note: The goal here is to show how bindings in custom @Components work.
The specific object doesn’t really matter.
Create a new package named logging and create a new file, HiltLogger.kt, with the
following code:
@TrackRunningScoped // 1
class HiltLogger @Inject constructor() {
raywenderlich.com 466
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
This class:
Now, create a new file named HiltLoggerEntryPoint.kt in the same package with
the following code:
@EntryPoint
@InstallIn(TrackRunningComponent::class) // HERE
interface HiltLoggerEntryPoint {
You already know how this code works. In this case, the only difference from the one
you implemented above is that you’re installing the binding into
TrackRunningComponent. This means you can’t use EntryPointAccessors because
TrackRunningComponent isn’t a @Component for an Android standard component.
Don’t worry, Hilt provides an API for this. You’ll see how to use it next.
@ExperimentalCoroutinesApi
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
// ...
private fun handleButtonState(newState: TrackerState?) {
with(startStopButton) {
if (newState is TrackerRunning) {
with(trackRunningComponentManager) {
startWith(System.currentTimeMillis())
with(newState.location) {
EntryPoints.get( // HERE
trackRunningComponent,
HiltLoggerEntryPoint::class.java
).logger().log("Lat: $latitude Long: $longitude")
}
raywenderlich.com 467
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
}
text = getString(R.string.stop_tracking)
setOnClickListener {
stopService(Intent(this@MainActivity,
TrackingService::class.java))
}
} else {
// ...
}
}
}
// ...
}
Build and run now and check that everything works as expected.
raywenderlich.com 468
Dagger by Tutorials Chapter 18: Hilt & Architecture Components
-94.39422
In this case, the HiltLogger instance during the first tracking was @32c174b. During
the second, it was @2c5fa3a. That means that a new instance of HiltLogger was
created at every session, as expected.
Key points
• Hilt currently doesn’t support all the Standard Android Components as
@AndroidEntryPoints.
• @DefineComponent needs a parent that allows you to extend the default Hilt
@Component hierarchy.
• Hilt provides a library to help you use dependency injection with ViewModel.
Congrats! In this chapter, you used some advanced Hilt APIs. You learned how to
create custom @Components that extend the existing Hilt @Component hierarchy. You
also learned how to use the libraries Hilt provides to manage dependency injection
with ViewModel.
But Hilt can do even more. In the next chapter, you’ll learn everything you need to
know about testing. See you there!
raywenderlich.com 469
19 Chapter 19: Testing With
Hilt
By Massimo Carli
In Chapter 17, “Hilt — Dagger Made Easy”, you learned that one of Hilt’s main goals
is to make testing easier. In the first chapter, you also learned that simplifying
testing is also one of the main reasons to use dependency injection.
Using constructor injection, you can create instances of classes to test, then pass
mocks or fakes directly to them as their primary constructor parameters. So where
does Hilt come in? How does it make tests easier to implement and run?
To understand this, think about what Dagger and Hilt actually are. Dagger gives you
a declarative way to define the dependency graph for a given app. Using @Modules
and @Components, you define which objects to create, what their lifecycles are and
how they depend upon one other.
Hilt makes things easier with a predefined set of @Components and @Scopes. In
particular, using @HiltAndroidApp, you define the main @EntryPoint for the app
bound to the Application lifecycle. You do the same with ActivityComponent,
ServiceComponent and others you met in the previous chapters. In general, you
define a dependency graph containing the instances of classes you inject.
Here’s the point. The objects you use when you run a test are, in most cases, not the
same objects you use when running the app. The dependency graph isn’t the same.
Hilt gives you an easy way to decide which objects are in the dependency graph for
the app and which objects are there for the tests.
raywenderlich.com 470
Dagger by Tutorials Chapter 19: Testing With Hilt
In this chapter, you’ll learn how Hilt helps you implement tests for your app. In
particular, you’ll see how to use Hilt to run:
• Robolectric UI tests
• Instrumentation tests
Hilt only supports Robolectric tests and instrumentation tests because with
constructor injection, it’s easy to implement unit tests without Dagger. You’ll see
how shortly.
To learn how to implement tests with Hilt, you’ll work on the RandomFunNumber
app. This simple app lets you push a button to get a random number and some
interesting facts about that number. It’s also perfect for learning about testing with
Hilt.
Note: As you know, Hilt is still in alpha version and so are its testing libraries.
During this chapter, you’ll implement some configuration caveats that need to
be there to make the tests run successfully. Some of these solve bugs in the
library that might be resolved by the time you read this chapter.
raywenderlich.com 471
Dagger by Tutorials Chapter 19: Testing With Hilt
raywenderlich.com 472
Dagger by Tutorials Chapter 19: Testing With Hilt
This is a very simple app that contains everything you need to learn how to write
tests using the tools and API Hilt provides. Before doing that, however, you’ll learn
about RandomFunNumber’s:
• Architecture
• Hilt configuration
After you understand how the app works, you’ll start writing tests.
RandomFunNumber’s architecture
The class diagram in Figure 19.4 gives you a high-level description of
RandomFunNumber’s main components:
As you see:
raywenderlich.com 473
Dagger by Tutorials Chapter 19: Testing With Hilt
raywenderlich.com 474
Dagger by Tutorials Chapter 19: Testing With Hilt
The code in the starter project in the materials for this chapter contains quite a few
@Modules. One reason is modularization. For instance, NetworkingModule and
NavigationModule are in the libs.networking and libs.ui.navigation modules.
Another reason is testing. As you’ll see later, you’ll replace threading during tests.
That’s why the Scheduler’s bindings are in SchedulersModule and not directly in
ApplicationModule.
Keep in mind that most of the configurations are already in the starter project in the
material for this chapter.
raywenderlich.com 475
Dagger by Tutorials Chapter 19: Testing With Hilt
You’ll see each of these build types in detail in the following paragraphs. For the
moment, just open testShared and look at its content:
raywenderlich.com 476
Dagger by Tutorials Chapter 19: Testing With Hilt
Note: If you want to learn more about fakes, mocks and stubs, Android Test-
Driven Development by Tutorials (https://www.raywenderlich.com/books/
android-test-driven-development-by-tutorials) is the right place for you.
With this kind of testing, there’s not much benefit to using Dagger. To see this for
yourself, you’ll create a unit test for FunNumberViewModel.
raywenderlich.com 477
Dagger by Tutorials Chapter 19: Testing With Hilt
Now, select JUnit 4 for the testing library and click OK to get the screen in Figure
19.10:
class FunNumberViewModelTest {
}
Now, open the new FunNumberViewModelTest.kt and add the following code:
class FunNumberViewModelTest {
@Rule
@JvmField
val instantExecutorRule = InstantTaskExecutorRule() // 1
@Before
fun setUp() {
funNumberService = FakeFunNumberService()
objectUnderTest = FunNumberViewModel(funNumberService) // 4
}
@Test
fun `when refreshNumber invoked you observe FunNumber`() {
val expectedFunNumber = FunNumber(
88,
"Testing Text",
true,
"default"
)
funNumberService.resultToReturn = expectedFunNumber
objectUnderTest.refreshNumber() // 5
val result =
objectUnderTest.numberFunFacts.getOrAwaitValue() // 6
assertEquals(expectedFunNumber, result) // 7
raywenderlich.com 478
Dagger by Tutorials Chapter 19: Testing With Hilt
}
}
2. This is the property for the instance of the object to test. In this case, it’s
FunNumberViewModel.
raywenderlich.com 479
Dagger by Tutorials Chapter 19: Testing With Hilt
1. Constructor injection allows you to create the instance of the object under test
by passing the reference to fakes or mocks as their primary constructor
parameter.
1. Use Robolectric.
You’re still working in the test build type, so it’s time for Robolectric. :]
raywenderlich.com 480
Dagger by Tutorials Chapter 19: Testing With Hilt
For your next step, you’ll use Robolectric and Hilt to run tests for:
• MainActivity
• FunNumberFragment
Before the actual test implementation, however, you need to do some setup.
// ...
dependencies {
// ...
// Hilt for Robolectric tests.
testImplementation "com.google.dagger:hilt-android-testing:
$hilt_version" // 1
kaptTest "com.google.dagger:hilt-android-compiler:
$hilt_version" // 2
}
Here, you:
1. Add the dependency to use Hilt testing with Robolectric. This is a definition for
the test build type.
The version is the same as the main Hilt library’s dependency has. Also, note how
testing with Hilt requires you to install an annotation processor because it will have
to generate some code.
raywenderlich.com 481
Dagger by Tutorials Chapter 19: Testing With Hilt
@AndroidEntryPoint // 1
class MainActivity : AppCompatActivity() {
@Inject
lateinit var navigator: Navigator // 2
Next, you want to create a test that verifies that the navigator displays
FunNumberFragment properly. Start by using the process shown in Figures 19.8-10,
create a new RoboMainActivityTest.kt in the test build type.
raywenderlich.com 482
Dagger by Tutorials Chapter 19: Testing With Hilt
class RoboMainActivityTest
Before you write the actual test, you need some initial configuration. Change the
previous code to this:
@HiltAndroidTest // 1
@Config(application = HiltTestApplication::class) // 2
@RunWith(RobolectricTestRunner::class) // 3
class RoboMainActivityTest {
@get:Rule
var hiltAndroidRule = HiltAndroidRule(this) // 4
@Before
fun setUp() {
hiltAndroidRule.inject() // 5
}
@Test
fun whenMainActivityLaunchedNavigatorIsInvokedForFragment()
{ // 6
assertTrue(true)
}
}
This code contains a very important configuration you’ll use in the following tests,
as well. Here’s what’s going on:
1. You annotate the test with @HiltAndroidTest. This enables the Hilt annotation
processor to generate the code to create the dependency graph for the test.
raywenderlich.com 483
Dagger by Tutorials Chapter 19: Testing With Hilt
6. Define the function for the test in this file. At the moment, you’re asserting
something that you know is true, just to have something to run.
Before running this test, you need to open robolectric.properties from resources in
the test build type. Its initial content is:
sdk=28
Robolectric requires this to avoid an annoying error on the API Level of the Android
environment you’re testing against. To avoid using what’s at the previous point 2,
add the following line:
sdk=28
application=dagger.hilt.android.testing.HiltTestApplication #
HERE
Now, you can use Android Studio and run a successful test. Your next step is to
implement the test.
raywenderlich.com 484
Dagger by Tutorials Chapter 19: Testing With Hilt
Configuring ActivityScenario
ActivityScenario is part of an API Google provides for testing Activitys. To use
this, you need to add the following code to RoboMainActivityTest.kt, which you
created earlier:
@HiltAndroidTest
@Config(application = HiltTestApplication::class)
@RunWith(RobolectricTestRunner::class)
class RoboMainActivityTest {
@get:Rule(order = 0) // 2
var hiltAndroidRule = HiltAndroidRule(this)
@get:Rule(order = 1) // 2
var activityScenarioRule: ActivityScenarioRule<MainActivity> =
ActivityScenarioRule(MainActivity::class.java) // 1
// ...
@Test
fun whenMainActivityLaunchedNavigatorIsInvokedForFragment() {
activityScenarioRule.scenario // 3
}
}
2. Use the order attribute for the @get:Rule annotation. You need this when you
have more than one rule in the same file and you want to give them a specific
execution order. HiltAndroidRule needs to be the first rule to run — setting
order = 0 allows you to ensure that it is.
raywenderlich.com 485
Dagger by Tutorials Chapter 19: Testing With Hilt
Unfortunately, if you now run the test with Android Studio as you did before, you’ll
get the following error:
Don’t worry, this isn’t your fault. :] This is a bug that, at the moment, prevents you
from running this test from Android Studio.
It’s an empty test, though. How can you test that MainActivity actually works?
Look at its code and you see that MainActivity needs a Navigator.
@HiltAndroidTest
@Config(application = HiltTestApplication::class)
@RunWith(RobolectricTestRunner::class)
@UninstallModules(ActivityModule::class) // 1
class RoboMainActivityTest {
// ...
@BindValue // 2
@JvmField
val navigator: Navigator = FakeNavigator() // 3
@Test
fun whenMainActivityLaunchedNavigatorIsInvokedForFragment() {
activityScenarioRule.scenario
val fakeNav = navigator as FakeNavigator
assertNotNull(fakeNav.invokedWithDestination)
assertTrue(fakeNav.invokedWithDestination is
FragmentDestination<*>) // 4
}
}
raywenderlich.com 486
Dagger by Tutorials Chapter 19: Testing With Hilt
This code contains everything you need to know about Hilt and testing. As you can
see:
Now, run the test from the command line and check that it’s successful.
Keep these points in mind because you’ll use them many times in the following tests.
raywenderlich.com 487
Dagger by Tutorials Chapter 19: Testing With Hilt
Note: In the final project in the materials for this chapter, you’ll also find an
instrumentation test for MainActivity. Implementing it is a useful exercise.
In this section, you’ll create a more challenging test. This time, you’ll test
FunNumberFragment. Creating a test like this isn’t obvious and it requires some
preparation.
Usually, before you test a Fragment, you first launch an Activity as its container.
With Hilt, the problem is that if the Fragment is an @AndroidEntryPoint, the same
must be true for the container Activity. If you just use ActivityScenario, this
doesn’t happen automatically. You need to:
3. Implement a utility class to launch the Fragment under test into the Hilt-enabled
Activity.
raywenderlich.com 488
Dagger by Tutorials Chapter 19: Testing With Hilt
// ...
dependencies {
// Hilt for instrumented tests.
androidTestImplementation "com.google.dagger:hilt-android-
testing:$hilt_version" // 1
kaptAndroidTest "com.google.dagger:hilt-android-compiler:
$hilt_version" // 2
// ...
}
1. Add the dependency that lets you use Hilt testing in instrumentation tests.
That’s why the definition is for the androidTest build type.
Now, you’re ready to use Hilt testing libraries in the instrumentation tests for
RandomFunNumber.
@AndroidEntryPoint // HERE
class HiltActivityForTest : AppCompatActivity()
raywenderlich.com 489
Dagger by Tutorials Chapter 19: Testing With Hilt
<manifest xmlns:android="http://schemas.android.com/apk/res/
android"
package="com.raywenderlich.android.randomfunnumber">
<application>
<activity
android:name=".HiltActivityForTest"
android:exported="false" />
</application>
</manifest>
raywenderlich.com 490
Dagger by Tutorials Chapter 19: Testing With Hilt
Open FragmentTestUtil.kt in util in the androidTest build type and find the
extension function with the following signature:
The only thing you need to know is that you can now use
launchFragmentInHiltContainer() to launch a Fragment that’s an
@AndroidEntryPoint for Hilt.
Now, you need to set this as the default TestRunner implementation for
instrumentation tests.
raywenderlich.com 491
Dagger by Tutorials Chapter 19: Testing With Hilt
// ...
android {
// ...
defaultConfig {
applicationId "com.raywenderlich.android.randomfunnumber"
minSdkVersion 28
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner
"com.raywenderlich.android.randomfunnumber.runner.HiltTestRunner
" // HERE
}
// ...
}
// ...
@HiltAndroidTest
@UninstallModules(ActivityModule.Bindings::class) // 1
class FunNumberFragmentTest {
@get:Rule
var hiltAndroidRule = HiltAndroidRule(this) // 2
@BindValue
@JvmField
val funNumberService: FunNumberService =
FakeFunNumberService() // 3
@Before
fun setUp() {
hiltAndroidRule.inject() // 4
}
@Test
fun whenButtonPushedSomeResultDisplayed() {
(funNumberService as FakeFunNumberService).resultToReturn =
raywenderlich.com 492
Dagger by Tutorials Chapter 19: Testing With Hilt
onView(withId(R.id.fun_number_output)).check(matches(withText("1
23")))
onView(withId(R.id.fun_fact_output)).check(matches(withText("Fun
ny Number")))
}
}
Other than TestRunner’s configuration, this test isn’t much different from the one
you implemented using Robolectric. Here, you:
4. Invoke inject() on the hiltAndroidRule to trigger the injection into the test.
6. Use Espresso to check that the Fragment displays what it gets from
FunNumberService.
Now, use Android Studio to run the test and check that everything works as
expected.
Great! You learned how to implement the configuration you need to use Hilt with an
instrumented test. This example is similar to the one you implemented with
Robolectric.
This is actually almost everything you need to know about Hilt and testing. There’s
just one more thing to cover, but you’ll need a more complex example for it.
raywenderlich.com 493
Dagger by Tutorials Chapter 19: Testing With Hilt
In some cases, however, you need to replace an entire @Module with another. To see
an example of this, create a new file named FunNumberServiceImplHiltTest.kt in
the business package in the androidTest build type and add the following code:
@HiltAndroidTest
@UninstallModules( // 1
SchedulersModule::class,
NetworkModule::class,
ApplicationModule::class)
class FunNumberServiceImplHiltTest {
@Inject
lateinit var objectUnderTest: FunNumberServiceImpl
@Inject
@IOScheduler
lateinit var testScheduler: Scheduler // 2
@BindValue
@JvmField
val funNumberEndPoint: FunNumberEndpoint =
StubFunNumberEndpoint() // 3
@BindValue
@JvmField
val randomGenerator: NumberGenerator =
FakeNumberGenerator().apply { // 3
nextNumber = 123
}
@get:Rule
var hiltAndroidRule = HiltAndroidRule(this)
@Before
fun setUp() {
hiltAndroidRule.inject()
}
@Test
fun whenRandomFunNumberIsInvokedAResultIsReturned() {
val fakeCallback = FakeCallback<FunNumber>()
objectUnderTest.randomFunNumber(fakeCallback)
(testScheduler as TestScheduler).advanceTimeBy(100,
TimeUnit.MILLISECONDS)
raywenderlich.com 494
Dagger by Tutorials Chapter 19: Testing With Hilt
@Module
@InstallIn(ApplicationComponent::class) // 4
object SchedulersModule {
@Provides
@ApplicationScoped
@MainScheduler
fun provideMainScheduler(): Scheduler =
Schedulers.trampoline()
@Provides
@ApplicationScoped
@IOScheduler
fun provideIoScheduler(): Scheduler = TestScheduler()
}
}
This is the Hilt version of the test for FunNumberServiceImpl. You can find this
class, as a unit test, in the test build type in the final project in the materials for this
chapter.
The pattern is the same as you’ve seen in the previous examples. In this case, you:
2. Use @Inject to get the reference to some objects in the testing dependency
graph. In this case, you get the reference to TestScheduler, which you need for
RxJava.
raywenderlich.com 495
Dagger by Tutorials Chapter 19: Testing With Hilt
3. Use @BindValue to replace some of the objects in the testing dependency graph.
In this example, you learned that you can replace the binding of an entire @Module
by simply uninstalling the initial one and reinstalling a new one. You can define the
testing @Module in the same file as the test or as an external file, depending on
where you use the new @Module.
Key points
• Hilt provides a testing library for Robolectric and instrumented tests.
• You don’t need Dagger to implement unit tests if you use constructor injection.
• Hilt allows you to replace parts of the app’s dependency graph for testing
purposes.
• You can remove bindings from the dependency graph using @UninstallModules
and replace some of them using @BindValue.
• You can replace all the bindings for a @Module by uninstalling it with
@UninstallModules and installing a new @Module.
Great job! In this chapter, you saw how to modify the dependency graph of your app
to make Robolectric and instrumentation tests easier to implement and run. You’ve
now learned everything you need to know about Hilt.
This is the last chapter of this book that covers Dagger and Hilt. In the very final
chapter, you’ll see how you can implement dependency injection in the Busso
Server back-end app.
raywenderlich.com 496
C Conclusion
Congratulations! After a long journey, you have learned a lot of concepts about
dependency injection. You can implement dependecy injection manually, or you can
use the Dagger and Hilt libraries provided by Google.
And, remember, if you want to further your understanding of Kotlin and Android app
development after working through Dagger by Tutorials, we suggest you read the
Android Apprentice and Kotlin Apprentice available on our online store:
• https://www.raywenderlich.com/books/android-apprentice
• https://www.raywenderlich.com/books/kotlin-apprentice
If you have any questions or comments as you work through this book, please stop by
our forums at http://forums.raywenderlich.com and look for the particular forum
category for this book.
Thank you again for purchasing this book. Your continued support is what makes the
tutorials, books, videos, conferences and other things we do at raywenderlich.com
possible, and we truly appreciate it!
Wishing you all the best in your continued Kotlin and saving data on Android.
raywenderlich.com 497
Section VI: Appendices
raywenderlich.com 498
A Appendix A: The Busso
Server
By Massimo Carli
In this book, you learned everything you need to use Dagger and Hilt. You did this by
working on several different apps, with Busso being the most complex of them. As
mentioned in the first chapter, Busso needs a server: the BussoServer. This is a web
application implemented using Ktor, which is the framework for implementing web
services with Kotlin. In this last chapter, you’ll look at how:
raywenderlich.com 499
Dagger by Tutorials Appendix A: The Busso Server
raywenderlich.com 500
Dagger by Tutorials Appendix A: The Busso Server
Now, open Application.kt in the main package and look at its content:
// 1
fun main(args: Array<String>): Unit =
io.ktor.server.netty.EngineMain.main(args)
@KtorExperimentalLocationsAPI
@Suppress("unused") // Referenced in application.conf
fun Application.module() { // 2
// 3
install(Locations)
// 4
install(ContentNegotiation) {
gson {
setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
}
}
// 5
routing {
get("/") { // 6
call.respondText("I'm working!!", contentType =
ContentType.Text.Plain)
}
// Features // 7
findBusStop()
findBusArrivals()
myLocation()
weather()
}
}
1. You define main() for the app by instantiating the engine that implements all
the routing logic for the server. Here, you’re using Netty (https://netty.io/).
2. The server engine needs some configuration that you put into a module. You
usually provide this information using a module() extension function.
3. Ktor allows you to install and use different plugins for different purposes. In this
case, you install the Locations feature. Despite its name, that feature has
nothing to do with locations on a map. Rather, it gives you a type-safe way to
define routing. You’ll see this in action later. Locations is experimental and
requires the @KtorExperimentalLocationsAPI annotation.
4. You use the ContentNegotiation feature, which allows you to manage JSON as
the transport protocol. In this case, you use Gson as the parser.
5. The routing is the logic that maps a specific URI pattern to some logic. You define
routings in a routing block.
raywenderlich.com 501
Dagger by Tutorials Appendix A: The Busso Server
6. Just for testing, if the server is up and running, you usually define a very simple
endpoint for the root path /. In this case, you just return a simple text. Note how
you send back content using respondText() on a call you get by invoking
get() for a given path.
7. The remaining function allows you to install other endpoints for different paths.
You’ll see how to do this soon.
To launch the server locally, you just need to click on the run icon, as shown in
Figure 20.3:
Now, open the browser and access http://127.0.0.1:8080. You’ll get the testing
message that tells you that everything works, as in Figure 20.4:
raywenderlich.com 502
Dagger by Tutorials Appendix A: The Busso Server
// 1
private val busStopRepository = ResourceBusStopRepository() // 2
@KtorExperimentalLocationsAPI
@Location(FIND_BUS_STOP) // 3
data class FindBusStopRequest(
val lat: Float,
val lng: Float
)
@KtorExperimentalLocationsAPI
fun Route.findBusStop() { // 4
get<FindBusStopRequest> { inputLocation -> // 5
// If there's a radius we add it as distance
val radius = call.parameters.get("radius")?.toInt() ?: 0
call.respond(
busStopRepository.findBusStopByLocation( // 6
inputLocation.lat,
inputLocation.lng,
radius
)
)
}
}
The structure of this code is similar to that of the other endpoints. Here, you:
1. Define FIND_BUS_STOP with the path of the URL you invoke to access the
endpoint. You pass the latitude and longitude in the input with the lat and lng
query parameters.
5. When you receive a request, the input parameters are encapsulated into
FindBusStopRequest, which you receive as an implicit parameter for get().
raywenderlich.com 503
Dagger by Tutorials Appendix A: The Busso Server
Other than the specific logic, something might not look right in this code: In 2, you
create an instance of ResourceBusStopRepository. This is a composition
relationship, which you’ve learned is something to avoid. Even worse, you have the
same relationship in FindBusArrival.kt.
How can you improve this? A very simple option is to use Koin.
This is a simple example, but it gives you an idea about how to use Koin and how it
differs from Dagger and Hilt.
raywenderlich.com 504
Dagger by Tutorials Appendix A: The Busso Server
// ...
dependencies {
// ...
implementation "org.koin:koin-core:$koin_version" // 1
implementation "org.koin:koin-ktor:$koin_version" // 2
testImplementation "org.koin:koin-test:$koin_version" // 3
// ...
}
// ...
raywenderlich.com 505
Dagger by Tutorials Appendix A: The Busso Server
After you sync the BussoServer project with the Gradle file you just updated, you’re
ready to use Koin.
It’s important to note that, at this point, the relationships at points 3 and 4 are
compositions. Your goal is to use dependency injection to make those
dependencies loosely coupled.
raywenderlich.com 506
Dagger by Tutorials Appendix A: The Busso Server
To see this for yourself, create a new package named di and create a new file named
RepositoryModule.kt in it with the following code:
3. Do the same for the BusArrivalRepository. Every time you inject an object of
type BusArrivalRepository, you’ll use the instance of
RandomBusArrivalRepository you created here.
@KtorExperimentalLocationsAPI
@Suppress("unused") // Referenced in application.conf
fun Application.module() {
install(org.koin.ktor.ext.Koin) { // 1
modules(repositoryModule) // 2
}
// ...
}
// ...
raywenderlich.com 507
Dagger by Tutorials Appendix A: The Busso Server
1. Use install to register the Koin feature with Ktor, just as you did for the
Locations and ContentNegotiation features. It’s important to use
org.koin.ktor.ext as the Koin class’ package.
2. Declare which modules to use. In your case, you use modules(), passing the
reference to the repositoryModule you defined above.
// ...
@KtorExperimentalLocationsAPI
fun Route.findBusStop() {
raywenderlich.com 508
Dagger by Tutorials Appendix A: The Busso Server
Now, open FindBusArrivals.kt in the same apis package and add the following
code:
2. Use inject() to inject the instances from repositoryModule, just as you did for
FindBusStop before.
Finally, run BussoServer to see that everything works as expected. To verify this,
open the browser and access a URL like http://localhost:8080/api/v1/findBusStop/
1.0/2.0.
raywenderlich.com 509
Dagger by Tutorials Appendix A: The Busso Server
Suppose you now want to add a simple logger to check that the instances you’re
using in the app are singletons or, using Koin language, are single. To do this, you
just need to:
raywenderlich.com 510
Dagger by Tutorials Appendix A: The Busso Server
interface Logger {
Now, you just need a very simple implementation. So in the same package, create a
new file named StdLoggerImpl.kt and add the following code:
This implementation uses the built-in function print() to write the log message to
the standard output.
1. Define loggerModule as a Koin module in the same way you did for the
repositories.
2. Used factory instead of single to define the binding of the Logger type to an
instance of StdLoggerImpl.
raywenderlich.com 511
Dagger by Tutorials Appendix A: The Busso Server
You could have used single again but you’re using factory to show something
different. In this case, you’ll get a different instance of Logger every time you inject
one.
@KtorExperimentalLocationsAPI
@Suppress("unused") // Referenced in application.conf
fun Application.module() {
install(org.koin.ktor.ext.Koin) {
modules(repositoryModule)
modules(loggerModule) // HERE
}
// ...
}
Here, you simply install loggerModule using modules(), as you did for
repositoryModule earlier.
Great! Now, BussoServer knows that there’s a Logger somewhere — but it doesn’t
know how to use it or which classes need it.
Creating dependencies
Suppose you now want to use the Logger in ResourceBusStopRepository and
RandomBusArrivalRepository. To do this, you need to define the dependency by
using — of course — constructor injection.
init {
logger.log("Initializing ResourceBusStopRepository:
$this") // 2
raywenderlich.com 512
Dagger by Tutorials Appendix A: The Busso Server
val jsonAsText =
this::class.java.getResource(BUS_STOP_RESOURCE_PATH).readText()
model = Gson().fromJson(jsonAsText,
BusStopData::class.java).apply {
items.forEach { butStop ->
this@apply.stopMap[butStop.id] = butStop
}
}
}
In all the logs, you also print the specific instance of ResourceBusStopRepository
you’re using. You’ll use this to prove that ResourceBusStopRepository is actually a
singleton.
Now, you need to do the same for the other repository. Open
RandomBusArrivalRepository.kt and apply similar changes, like this:
/**
* Number of arrivals for line
*/
raywenderlich.com 513
Dagger by Tutorials Appendix A: The Busso Server
/**
* Implementation for the BusArrivalRepository which returns
random values
*/
class RandomBusArrivalRepository constructor(
private val busStopRepository: BusStopRepository, // 2
private val logger: Logger // 3
) : BusArrivalRepository {
override suspend fun findBusArrival(busStopId: String):
List<BusArrivalGroup> {
logger.log("Invoking findBusArrival for id: $busStopId on
$this") // 4
val busStop = busStopRepository.findBusStopById(busStopId)
if (busStop == null) {
return emptyList()
}
return mutableListOf<BusArrivalGroup>().apply {
arrivalGroupRange().forEach {
add(
BusArrivalGroup(
lineId = "1",
lineName = lines.random(),
destination = destinations.random(),
arrivals = generateRandomBusArrival()
)
)
}
}
}
In this code, you also see something you didn’t know before.
RandomBusArrivalRepository actually needs a BusStopRepository — so it
depends on it. That’s because you:
raywenderlich.com 514
Dagger by Tutorials Appendix A: The Busso Server
Build now and you’ll get an error. You just added dependencies to
ResourceBusStopRepository and RandomBusArrivalRepository, but Koin doesn’t
know who’s providing those dependencies. You need to fix RepositoryModule.
All you need to do is add get() every time you need to resolve a dependency. In this
code, you use:
Now, build and run, checking that everything works as expected, as you did in Figure
20.7.
More interesting is to prove that the instance of the repositories is always the same
at each request. Check in Logcat and you’ll see something like this:
Initializing ResourceBusStopRepository:
com...ResourceBusStopRepository@6456c628
findBusStopByLocation on
com...ResourceBusStopRepository@6456c628 with lat:1.0 lon: 2.0
findBusStopByLocation on com..ResourceBusStopRepository@6456c628
with lat:1.0 lon: 2.0
findBusStopByLocation on com..ResourceBusStopRepository@6456c628
with lat:1.0 lon: 2.0
raywenderlich.com 515
Dagger by Tutorials Appendix A: The Busso Server
Key points
• BussoServer is Busso’s server app. It’s a Ktor app.
• You can use dependency injection on a Ktor server using Koin, a fully Kotlin
solution without code generation.
• Like Dagger, Koin allows to install the definition of modules you need as a Ktor
feature.
• Using get(), you can manage transitive dependencies between different objects.
raywenderlich.com 516
B Appendix B: Assisted
Injection
By Massimo Carli
Dagger and Hilt are libraries in continuous evolution. Google and Square, with the
help of the open-source community, keep improving them, both by creating new
features and by improving the performance of the existing ones.
raywenderlich.com 517
Dagger by Tutorials Appendix B: Assisted Injection
This is very cool, but sometimes you need something a bit different. To understand
what, return to RandomFunNumber to see a practical example.
raywenderlich.com 518
Dagger by Tutorials Appendix B: Assisted Injection
hilt_version = "2.31.2-alpha"
This is important because assisted injection was introduced in Dagger 2.31, so you
need to use at least that version.
raywenderlich.com 519
Dagger by Tutorials Appendix B: Assisted Injection
1. Use @Inject to tell Dagger to use the primary constructor to create the
instances.
// ...
@Binds
@ActivityScoped
fun bindFunNumberService( // HERE
impl: FunNumberServiceImpl
): FunNumberService
// ...
This code just says that every time you need to inject an object of the type
FunNumberService, Dagger will create an instance of FunNumberServiceImpl to
handle the dependencies you saw earlier.
With this configuration, Dagger provides all the dependencies for you. But what if
you want to provide a different dependency every time you need a
FunNumberServiceImpl? That’s where assisted injection comes in handy.
However, while that would work, it would be quite verbose. Assisted injection is an
elegant alternative.
raywenderlich.com 520
Dagger by Tutorials Appendix B: Assisted Injection
4. To inject the Factory wherever you need a FunNumberService and provide the
proper dependency.
Now, you’re ready to code along and add assisted injection to RandomFunNumber.
This code replaces @Inject with @AssistedInject. This tells Dagger that you’ll
explicitly provide some of the dependencies you define as primary constructor
parameters.
However, the code above doesn’t tell Dagger which dependencies you’ll provide. For
that, you need @Assisted.
Using @Assisted
You just learned how to tell Dagger that you’ll handle some of
FunNumberServiceImpl’s dependencies. Now, you need to declare which
dependencies you’ll provide.
raywenderlich.com 521
Dagger by Tutorials Appendix B: Assisted Injection
When you add the import for @Assisted, you’ll probably see two different options.
That’s because there’s an @Assisted in the dagger.assisted package and another in
androidx.hilt. The source code in the latter has a comment saying that it’ll be
replaced with the former. Therefore, the one in dagger.assisted is the right one to
import in your class.
In this code, you use @Assisted for the primary constructor parameter of type
NumberGenerator. With this, you tell Dagger that, when it’s time to create the object
to bind to FunNumberService, you’ll explicitly provide the dependency of type
NumberGenerator.
Using @AssistedFactory
So far, you’ve told Dagger that:
1. You want to do part of its job by providing some of the dependencies for
FunNumberService’s binding.
@AssistedFactory // 1
interface FunNumberServiceFactory { // 2
fun create(
numberGenerator: NumberGenerator // 3
): FunNumberServiceImpl // 4
}
raywenderlich.com 522
Dagger by Tutorials Appendix B: Assisted Injection
3. Need a create operation that has the @Assisted dependencies as its parameters.
Earlier, you told Dagger that you’ll provide the dependency for
NumberGenerator. This parameter is how you do that.
Because of the last point, you can open ActivityModule.kt in di and change it to
this:
@Module(includes = [
NavigationModule::class
])
@InstallIn(ActivityComponent::class)
object ActivityModule
Now that you’ve defined FunNumberServiceFactory, Dagger will also create the
binding for you. This means you don’t need to add this definition to any @Module.
You can now simply @Inject the FunNumberServiceFactory where you need it — in
this case, in FunNumberViewModel.
Using FunNumberServiceFactory in
FunNumberFragment
You now need to inject FunNumberServiceFactory where you need a
FunNumberService.
@AndroidEntryPoint
class FunNumberFragment : Fragment() {
raywenderlich.com 523
Dagger by Tutorials Appendix B: Assisted Injection
@Inject
lateinit var funNumberServiceFactory:
FunNumberServiceFactory // 1
private lateinit var funNumberService: FunNumberService // 2
Note: In this code, you’re only practicing how to use assisted injection. The
instance of the NumberGenerator implementation you create here doesn’t
matter.
raywenderlich.com 524
Dagger by Tutorials Appendix B: Assisted Injection
raywenderlich.com 525
Dagger by Tutorials Appendix B: Assisted Injection
Key points
• Dagger has offered assisted injection since version 2.31.0.
raywenderlich.com 526