Unraveling the Mysteries of Method Injection in Dagger: DI (Day4)

Photo by Max Duzij on Unsplash

Unraveling the Mysteries of Method Injection in Dagger: DI (Day4)

Hello there, fellow coder! If you've been following our series on dependency injection, you're already familiar with the basics and the workings of constructor and field injection. Today, we're going to dive deeper and explore another fascinating form of dependency injection: method injection.

Method Injection: A Closer Look

Let's start by demystifying what method injection is. In the realm of programming, method injection is a technique where dependencies are provided through methods. It's a form of inversion of control, where the responsibility of creating and managing dependencies is handed over to a library or container. This strategy enhances modularity, promotes testability, and increases flexibility in code. In the context of Dagger, a popular dependency injection framework, method injection comes into play when a class needs to pass a reference of itself to one of its dependencies. Intriguing, isn't it?

A Practical Guide to Implementing Method Injection

Now that we've got the theory down, let's roll up our sleeves and dive into some code. Imagine a class named MethodDependency. This class has a single method, which requires the activity instance to display a toast message on the UI. Here's how it looks:

public class MethodDependency {
    String TAG = this.getClass().getCanonicalName();
    @Inject
    MethodDependency(){
        Log.e(TAG, "Method dependency was created");
    }
    public void setActivity(MainActivity mainActivity){
        mainActivity.getCheckbox();
        Log.e(TAG, "Toast Message was created");
        Toast.makeText(mainActivity, "Toast from Method Dependency",
        Toast.LENGTH_SHORT).show();
    }
}

This class, MethodDependency, is a simple Java class that has a method setActivity(MainActivity mainActivity). This method requires an instance of MainActivity to show a toast message on the UI. The @Inject annotation on the constructor indicates that Dagger should use this constructor to create instances of MethodDependency.

Now, let's see how we can perform method injection in the MainActivity.

lateinit var methodDependency: MethodDependency
@Inject
fun setMyDependency(methodDependency: MethodDependency ) {
    this.methodDependency = methodDependency
    this.methodDependency.setActivity(this)
}

This code snippet is part of the MainActivity class. The setMyDependency method is annotated with @Inject, which tells Dagger to call this method and pass in the required dependencies, in this case, MethodDependency. This method then calls the setActivity method on MethodDependency, passing in the MainActivity instance

The final MainActivity code should look like this:

class MainActivity : AppCompatActivity() {
    var calculate : Button? = null
    var TAG = this.javaClass.canonicalName
    @Inject
    lateinit var computation : ComputeLayer
    lateinit var methodDependency: MethodDependency
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.e(TAG, "Main Activity Created Ready to be Injected")

        calculate = findViewById(R.id.calculate_sum)
        checkbox = findViewById(R.id.checkbox)

        val component = DaggerComputeComponent.create()
        component.inject(this)

        calculate?.setOnClickListener {
            computation.add(1,1)
            Log.e(TAG, "Button click detected")
        }

        Log.e(TAG, "Main Activity Created")
    }
    @Inject
    fun setMyDependency(methodDependency: MethodDependency ) {
        this.methodDependency = methodDependency
        this.methodDependency.setActivity(this)
    }
}

The code above poses a question in front of us. How would Dagger2 decide when to call the setMyDependency method and when to create the other dependencies since it's neither a constructor which is called right when the class is created, nor a field injection which is called right after that.

Decoding the Order of Dependency Creation in Dagger

Dagger2 follows a specific order for creating and providing dependencies of a class. This order is Constructor Injection > Field Injection > Method Injection. This means that dependencies injected through constructors are created first, followed by those injected through fields, and finally, those injected through methods. This order is crucial to understand as it determines the lifecycle and availability of different dependencies within a class.

For instance, a field-injected dependency won't be available in the constructor. Similarly, a method-injected dependency won't be available in the constructor or field. Understanding this order can help prevent null pointer exceptions and other runtime errors.

Order of Dependency Creation in Dagger

Navigating the Maze of Circular Dependency Injection

Circular dependencies occur when two or more classes depend on each other. For instance, Class A depends on Class B, and Class B depends on Class A. In the context of dependency injection, this can occur when a class that is being injected depends on the class into which it is injected.

In our example, we have a MainActivity that depends on MethodDependency, and MethodDependency that depends on MainActivity. This creates a circular dependency.

Circular Dependency Injection Example

And if you remember from our previous blog, we had mentioned that Dagger2 uses something called Directed Acyclic Graphs to decide the order in which dependencies should be created and it can never be cyclic in nature! So then why didn't we get an error during compile time?

Well, Dagger 2 provides a way to handle situations that might otherwise result in a circular dependency. It does this by delaying the resolution of one of the dependencies in the cycle until after the object has been constructed. This effectively breaks the cycle in the graph, allowing Dagger 2 to handle the situation.

To validate this, let us check the logcat to see the order in which all the classes were created.

And as expected, you can see that the Method Dependency was the last dependency to be created while other layers were created before that.

Why Method Injection?

You might be wondering if we already have Constructor and Field Injection then why do we need Method Injection?

So while method injection is used rarely, it could be helpful in some special cases as explained below:

  1. Post-construction initialization: There might be cases where you need to perform some initialization on an object after it has been constructed and its dependencies have been injected. Method Injection allows you to do this. Dagger 2 will call the injection method immediately after the constructor (for Constructor Injection) or after the fields have been injected (for Field Injection), allowing you to perform any necessary post-construction initialization.

  2. Injecting into classes you don't control: In Android, there are many classes (like Activities, Fragments, and Services) that are instantiated by the system, not by you. This means you can't use Constructor Injection because you don't control the constructor. Field Injection can be used in these cases, but it requires the class to be aware of the Dagger 2 component or subcomponent, which might not always be desirable. Method Injection provides another option. You can define an injection method in your class, and Dagger 2 will call it at the appropriate time.

  3. Injecting multiple dependencies of the same type: If you have multiple dependencies of the same type and you want to inject them separately (perhaps because they're configured differently), Method Injection can be useful. With Constructor and Field Injection, Dagger 2 will inject the same instance into all fields of the same type. But with Method Injection, you can have multiple methods each accepting a different instance of the same type.

Wrapping Up

Method injection is a powerful tool in Dagger 2 that allows for more flexible dependency management. But remember, while Method Injection can be useful in these and other scenarios, it's generally recommended to prefer Constructor Injection where possible, as it ensures that an object's dependencies are set as soon as it's created, making it less likely that you'll end up with a partially initialized object. Field Injection and Method Injection should be used sparingly and only when necessary.