[MUSIC PLAYING] MANUEL VICENTE
VIVO: Hi, everyone. Welcome to the first episode
of the Hilt MAD Skills series. This first one is
an introduction to dependency injection,
or VI for short, on Hilt, Jetpack's recommended
solution for DI100. By following the principles
of dependency injection in your Android app,
you lay the groundwork for a good app architecture. It helps with reusability of
code, ease of refactoring, and ease of testing. With dependency
injection, instead of car creating its own
instance of engine, car receives an
engine, for example, as a parameter in
its constructor. This makes the car
class reusable, as it can be used with different
implementations of engine, and it's also easier to test. The dependency graph
of your application can be represented as using the
screen with classes as nodes, and arrows that indicate the
dependencies between classes. When creating instances of
classes in your project, you can exercise the
dependency graph manually by satisfying the dependencies
on transitive dependencies that the class requires. But doing this
manually every time involves some boilerplate code,
and could be a real problem. See for example, one
of the ViewModels that we have in iosched, the
open source Google IO app. Can you imagine the
amount of code required to create a FeedViewModel with
its dependencies and transitive dependencies? It is hard, repetitive,
and we could easily get the dependencies wrong. By using a dependency
injection library, we can get the benefits
of DI without having to provide the
dependencies manually, as the library generates all
the necessary code for you. And here is where
Hilt comes into play. Hilt is a dependency
injection library developed by Google that helps
you get the most out of DI best practices by
doing the hard work, and generating all
the boilerplates you would definitely need
to write otherwise. By using annotations, Hilt
generates that code for you at compile time, making
it really fast at runtime. This is done using the power
of Dagger, a JVM DI library that Hilt is built on top of. Hilt is Jetpack's recommended
DI solution for Android apps, and it comes with tooling and
other Jetpack library support. Let's see how do we get
started with Hilt in your app. All apps that use Hilt
must contain an application class that is annotated
with @HiltAndroidApp, since this triggers Hilt's code
generation at compile time. For Hilt to be able to inject
dependencies into an activity, the activity needs
to be annotated with @AndroidEntryPoint. To inject a dependency,
annotate the variables you want Hilt to
inject with @Inject. All Hilt-injected
variables will be available when super.onCreate is called. In this example, we are
injecting a MusicPlayer into PlayActivity. But how does Hilt
know how to provide instances of type MusicPlayer? Well, it doesn't, at the moment. We need to let Hilt
know how to do it, using annotations, of course. Annotating the constructor
of a class with @Inject tells Hilt how to create
instances of that class. This is what's needed to
get a dependency injected into an activity. We started with
a simple example, as MusicPlayer doesn't
depend on any other type. But if we had other dependencies
passed as a parameter, Hilt would take care of that
and satisfy those dependencies when providing an
instance of MusicPlayer. This was, in fact, a very
simple and naive example. But if you had to do what we've
done so far manually, how would you do that? When doing DI manually, you
can have dependency container classes that are responsible
for providing types and managing the lifecycle of the
instances that it provides. Oversimplifying a
bit here, that's what Hilt does under the hood. When you annotate the activity
with @AndroidEntryPoint, a dependency container is
automatically created, managed, and associated to PlayActivity. Let's call our manual
implementation of it PlayActivityContainer. By annotating
MusicPlayer with @Inject, we are basically
telling the container how to provide instances
of type MusicPlayer. And in the activity,
we would need to create an instance
of the container and populate the dependencies
of the activity using it. That's also done by Hilt
when annotating the activity with @AndroidEntryPoint. So far, we've seen
that when @Inject is used to annotate the
construction of a class, it tells Hilt how to provide
instances of that class. And when it annotates a variable
in an @AndroidEntryPoint annotated class, Hilt injects
an instance of that type into the class. @AndroidEntryPoint that can
annotate most Android framework classes, not only activities,
creates an instance of a dependency
container for that class, and populates all @Inject
annotated variables. @HiltAndroidApp annotates
the application class, and apart from triggering
Hilt's code generation, it also creates a dependency
container associated with the application class. Now that we got the
basics of Hilt covered, let's complicate the example. Now, MusicPlayer takes a
dependency in its constructor, MusicDatabase. Therefore, we need to
tell Hilt how to provide instances of MusicDatabase. When the type is an interface,
or you don't own the class, because it comes from
a library, for example, you cannot annotate its
constructor with @Inject. Let's imagine that we are
using a Room as the persistence library in our app. Back to our manual
implementation of PlayActivityContainer,
when providing MusicDatabase that, with Room, this
would be an abstract class, we would like to
run some code when providing the dependency--
in this case, the code you see on the screen. Then, when providing an
instance of MusicPlayer, we need to call the method
that provides or satisfies the MusicDatabase dependency. We don't need to worry
about that in Hilt, since it works up all transitive
dependencies automatically. However, we need to let it
know how to provide instances of type MusicDatabase. For that, we use Hilt modules. A Hilt module is a class
annotated with @Module, and within the
class, we can have functions that tell
Hilt how to provide instances of certain types. This information,
known by Hilt, is also called binding in Hilt jargon. This function that is
annotated with @Provides tells Hilt how to provide
instances of the MusicDatabase type. The body contains
the block of code that Hilt needs
to execute, which is exactly the same as we had
in our manual implementation of PlayActivityContainer. The return type MusicDatabase
informs Hilt about what type this function provides,
and the function parameters tell Hilt the dependencies
of the corresponding type-- in this case, the
application context that is already available in
Hilt. This code informs Hilt about how to provide instances
of the MusicDatabase type, or in other words, we have a
binding for MusicDatabase Hilt modules are also annotated
with the @InstallIn annotation that indicates in
which dependency containers or components this
information is available. But what is a component? Let's cover this in more detail. A component is a class
that Hilt generates that is responsible for
providing instances of types, like the container we've
been programming manually. At compile time, Hilt
reverses the dependency graph of your application
and generates code to provide all types with
their transitive dependencies. Hilt generates a component
or dependency container for most Android
framework classes. The information, or
bindings, of each component propagates through the
components hierarchy If the music database
binding is available in the SingletonComponent that
corresponds to the application class, it will also be available
in the rest of components. In Hilt jargon, you could say
that ActivityRetainedComponent and ServiceComponents
are super-components of singleton components. These components are
automatically generated by Hilt at compile time, and they
are created, managed, and associated with the
corresponding Android framework class when you
annotate those classes with @AndroidEntryPoint. The @InstallIn
annotation for modules is useful to control where
those bindings are available, and what other
bindings they can use. OK, back to our manually created
PlayActivityContainer code, I'm not sure if you
realized, but every time that the MusicDatabase
dependency is required, we are creating a
different instance of it. That's not ideal,
since we might want to use the same instance
of MusicDatabase throughout the whole app. Instead of a function, we
could share the same instance by having all that
code in a variable. Basically, we are scoping
the MusicDatabase type to this class, as we are always
providing the same instance. How to do this with Hilt? Well, no surprise here--
with another annotation. You guessed it. By using the @Singleton notation
in the @Provides method, we are telling Hilt to
always use the same instance of this type in that component. @Singleton is a
scope annotation. And its Hilt component has an
associated scope annotation. If you want to scope a type
to the ActivityComponent, you would use the
@ActivityScoped annotation. These annotations can
be used in modules, but they can also annotate
classes whose constructor is annotated with @Inject. With this, there are
two types of bindings. Bindings that are not annotated
with a scope annotation are called unscoped
bindings, like MusicPlayer. And these bindings are
available to all components if they are not
installed in a module. Scoped bindings that are
annotated with a scope annotation, like MusicDatabase,
or unscoped bindings that are installed
in a module, are available in the
corresponding component, and the ones below it in
the components hierarchy. Hilt also offers integrations
with the most popular Jetpack libraries-- ViewModel, Navigation,
Compose, and WorkManager. Apart from ViewModel,
its integration requires a different library
to add to your project. Check the documentation for
more information about it. Do you remember the
FeedViewModel code from iosched that we saw at the
beginning of the episode? Did you want to see how it
looks with Hilt support? There we go. Apart from annotating the
constructor with @Inject to let Hilt know how to provide
instances of this ViewModel, we need to annotate the
class with @HiltViewModel. That's it. You don't manually need to
create a ViewModel provider for this. Hilt will take care of that. Curious to see how to get this
ViewModel from the fragment? Here it is, as if you were
introducing Hilt. Using by ViewModels is enough if
the activity of fragment is annotated with
@AndroidEntryPoint. In Compose, it's
mostly the same. You can use the
standard ViewModel function available in Compose. As I mentioned
earlier, Hilt is built on top of another popular
dependency injection library, Dagger. Dagger will be often mentioned
in the next episodes. If you are using
Dagger, Dagger and Hilt can work together, with
more about the migration APIs in the guide linked
in the description below. For more information
about Hilt, we have a cheat sheet with the
most popular annotations, what they do, and
how to use them. Apart from our docs
on Hilt, we also have Codelabs to learn with
a more hands on experience, and open source
apps that use Hilt. And that's it for this episode. Thank you for making
it until the end. I hope you learnt a lot about
dependency injection and Hilt. But this doesn't end here. We have more MAD Skills
episodes coming up, so please subscribe to the
Android Developers YouTube channel to get notified
when they are posted. Bye. [MUSIC PLAYING]