Android – An Overview Of Dependency Injection In Android

Hello Readers, CoolMonkTechie heartily welcomes you in this article (An Overview Of Dependency Injection In Android).

In this article, we will learn about Dependency Injection in android. Dependency Injection (DI) is a technique widely used in programming and well suited to Android development. By following the principles of DI, we lay the groundwork for good app architecture. This article explains an overview of how Dependency Injection (DI) works in Android.

A famous quote about learning is :

” Wisdom is not a product of schooling but of the lifelong attempt to acquire it.”

So let’s begin.

An Overview Of Dependency Injection

Dependency injection is based on the Inversion of Control principle in which generic code controls the execution of specific code.

Classes often require references to other classes. For example, a Car class might need a reference to an Engine class. These required classes are called dependencies, and in this example the Car class is dependent on having an instance of the Engine class to run.

There are three ways for a class to get an object it needs:

  1. The class constructs the dependency it needs. In the example above, Car would create and initialize its own instance of Engine.
  2. Grab it from somewhere else. Some Android APIs, such as Context getters and getSystemService(), work this way.
  3. Have it supplied as a parameter. The app can provide these dependencies when the class is constructed or pass them in to the functions that need each dependency. In the example above, the Car constructor would receive Engine as a parameter.

With this Dependency Injection (DI) approach, we take the dependencies of a class and provide them rather than having the class instance obtain them itself.

Example Without Dependency Injection

This example is representing a Car that creates its own Engine dependency in code looks like this:

class Car {

    private val engine = Engine()

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.start()
}

This is not an example of dependency injection because the Car class is constructing its own Engine.

Source : Android Developers – Example Car Engine Without DI

This can be problematic because:

  • Car and Engine are tightly coupled – an instance of Car uses one type of Engine, and no subclasses or alternative implementations can easily be used. If the Car were to construct its own Engine, we would have to create two types of Car instead of just reusing the same Car for engines of type Gas and Electric.
  • The hard dependency on Engine makes testing more difficult. Car uses a real instance of Engine, thus preventing us from using a test double to modify Engine for different test cases.

Example With Dependency Injection

With this example, instead of each instance of Car constructing its own Engine object on initialization, it receives an Engine object as a parameter in its constructor. The code looks like this.

class Car(private val engine: Engine) {
    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val engine = Engine()
    val car = Car(engine)
    car.start()
}

The main function uses Car. Because Car depends on Engine, the application creates an instance of Engine and then uses it to construct an instance of Car.

Source : Android Developers – Example Car Engine With DI

The benefits of this DI-based approach are:

  • Reusability of Car: We can pass in different implementations of Engine to Car. For example, we might define a new subclass of Engine called ElectricEngine that we want Car to use. If we use DI, all we need to do is pass in an instance of the updated ElectricEngine subclass, and Car still works without any further changes.
  • Easy testing of Car: We can pass in test doubles to test our different scenarios. For example, we might create a test double of Engine called FakeEngine and configure it for different tests.

Major Ways to do Dependency Injection

There are two major ways to do dependency injection in Android:

  • Constructor Injection. This is the way described above. We pass the dependencies of a class to its constructor.
  • Field Injection (or Setter Injection). Certain Android framework classes such as activities and fragments are instantiated by the system, so constructor injection is not possible. With field injection, dependencies are instantiated after the class is created. The code would look like this:
class Car {
    lateinit var engine: Engine

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.engine = Engine()
    car.start()
}

Automated Dependency Injection

In the previous example, we created, provided, and managed the dependencies of the different classes ourself, without relying on a library. This is called dependency injection by hand, or manual dependency injection. In the Car example, there was only one dependency, but more dependencies and classes can make manual injection of dependencies more tedious. Manual dependency injection also presents several problems:

  • For big apps, taking all the dependencies and connecting them correctly can require a large amount of boilerplate code. In a multi-layered architecture, in order to create an object for a top layer, we have to provide all the dependencies of the layers below it. As a concrete example, to build a real car we might need an engine, a transmission, a chassis, and other parts; and an engine in turn needs cylinders and spark plugs.
  • When we’re not able to construct dependencies before passing them in — for example when using lazy initializations or scoping objects to flows of our app — we need to write and maintain a custom container (or graph of dependencies) that manages the lifetimes of our dependencies in memory.

Dependency Injection Libraries

There are libraries that solve this problem by automating the process of creating and providing dependencies. They fit into two categories:

  • Reflection-based solutions that connect dependencies at runtime.
  • Static solutions that generate the code to connect dependencies at compile time.

Dagger is a popular dependency injection library for Java, Kotlin, and Android that is maintained by Google. It facilitates using DI in our app by creating and managing the graph of dependencies for us. It provides fully static and compile-time dependencies addressing many of the development and performance issues of reflection-based solutions such as Guice.

Alternatives To Dependency Injection

An alternative to dependency injection is using a service locator. The service locator design pattern also improves decoupling of classes from concrete dependencies. We create a class known as the service locator that creates and stores dependencies and then provides those dependencies on demand.

object ServiceLocator {
    fun getEngine(): Engine = Engine()
}

class Car {
    private val engine = ServiceLocator.getEngine()

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.start()
}

Dependency Injection Vs Service Locator Pattern

The service locator pattern is different from dependency injection in the way the elements are consumed. With the service locator pattern, classes have control and ask for objects to be injected; with dependency injection, the app has control and proactively injects the required objects.

The Comparisons between dependency injection and Service Locator Pattern are:

  • The collection of dependencies required by a service locator makes code harder to test because all the tests have to interact with the same global service locator.
  • Dependencies are encoded in the class implementation, not in the API surface. As a result, it’s harder to know what a class needs from the outside. As a result, changes to Car or the dependencies available in the service locator might result in runtime or test failures by causing references to fail.
  • Managing lifetimes of objects is more difficult if we want to scope to anything other than the lifetime of the entire app.

Use Hilt in Android Application

Hilt is Jetpack’s recommended library for dependency injection in Android. It defines a standard way to do DI in our application by providing containers for every Android class in our project and managing their lifecycles automatically for us.

Hilt is built on top of the popular DI library Dagger to benefit from the compile time correctness, runtime performance, scalability, and Android Studio support that Dagger provides.

Benefits Of Dependency Injection

Dependency injection provides our app with the following advantages:

  • Reusability of classes and decoupling of dependencies: It’s easier to swap out implementations of a dependency. Code reuse is improved because of inversion of control, and classes no longer control how their dependencies are created, but instead work with any configuration.
  • Ease of refactoring: The dependencies become a verifiable part of the API surface, so they can be checked at object-creation time or at compile time rather than being hidden as implementation details.
  • Ease of testing: A class doesn’t manage its dependencies, so when you’re testing it, you can pass in different implementations to test all of your different cases.

That’s all about in this article.

Related Other Articles / Posts

Conclusion

In this article, we understood about Dependency Injection fundamental in android. This article reviewed an overview of how Dependency Injection (DI) works in Android.

Thanks for reading! I hope you enjoyed and learned about DI concepts in Android. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Summary
Article Name
Android - An Overview Of Dependency Injection In Android
Description
This article reviews about an overview of how Dependency Injection (DI) works.It explains Dependency Injection vs Service locator in Android.
Author

2 thoughts on “Android – An Overview Of Dependency Injection In Android”

Leave a Reply

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

Exit mobile version