Hilt and dependency injection - MAD Skills

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[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]
Info
Channel: Android Developers
Views: 45,364
Rating: undefined out of 5
Keywords: purpose: Educate, pr_pr: Android, series: MAD Skills, GDS: Yes, introduction to hilt, hilt, what is hilt, how to use hilt, intro to hilt, hilt tutorial, hilt android, android hilt, mad skills, modern android development, developer, developers, android developer, android developers, google developers, android, google, Manuel Vivo, type: Screencast (0-10min)
Id: 1Zt6aIqZnqU
Channel Id: undefined
Length: 13min 5sec (785 seconds)
Published: Mon Aug 23 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.