Lazy and Provider Injection in Dagger2 : Day 13

Play this article

"Lazy" and "Provider" are two powerful constructs in Dagger2 that give developers more control over when and how dependencies are created and provided. Let's delve into the topic:

Lazy and Provider Injection in Dagger2

1. Introduction:

In standard Dagger2 injection, dependencies are created and provided as soon as the component is built. However, there might be scenarios where you'd want more control over the instantiation of these dependencies. This is where Lazy and Provider come into play.

2. Lazy Injection:

a. What is Lazy Injection?

  • Lazy<T> is a Dagger2 construct that allows deferred creation of a dependency.

b. Why use Lazy Injection?

  • Deferred Initialization: If a dependency is heavy and you don't need it immediately after the component is created, you can delay its instantiation.

  • Single Instantiation: The dependency is created only once, the first time it's requested. Subsequent requests will return the same instance.

c. How to use Lazy Injection?

If you remember our previous example, you might have noted that creating the NetworkLayer took some time due to which our launch time of the application was also quite long (around 5 seconds). But we were using our NetworkLayer only when we actually clicked the Calculate button, so it would make more sense to create the object only when we need it instead of creating it right when we create the dagger graph.

This is where Lazy implementation comes in handy.

Let's go back to our previous example and make some changes in our MainActivity code as shown below.

class MainActivity : AppCompatActivity() {
    var calculate : Button? = null
    var TAG = this.javaClass.canonicalName

//    @Inject
//    lateinit var computation : ComputeLayer

    @Inject
    lateinit var lazyComputation: Lazy<ComputeLayer>

//    @Inject
//    lateinit var computation2 : ComputeLayer
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        calculate = findViewById(R.id.calculate_sum)

        val component = (application as MyApp).appComponent.activityBuilder.build()

        component.inject(this)

//    Log.e(TAG, "computation 1 is : $computation + and second computation is $computation2" )

        calculate?.setOnClickListener {
            lazyComputation.get()?.add(1,1)
            Log.e(TAG, "Button click uses computeLayer to be ")
        }
    Log.e(TAG, "Main Activity Created")
    }
}

And that's all that you need to change, let's run the app and notice what's the difference.

  1. The first major difference you would have seen is the launch time of the app. It launches instantly as soon as we run the app, which was the expected behavior.

    If you go ahead and check the logs, you'll notice that none of the logs that were put in the constructors of the classes have been printed, this is because dagger2 hasn't yet created those objects, since we haven't had the need of it.

  2. Now, let's go ahead and click the Calculate button.

    Notice that now the button click takes a long time to return the result, this is because dagger2 is creating all the needed objects after the button click. You can also verify this by checking the logs and noticing that all the constructor logs are now getting printed.

Remember

  • You might have noticed above that Lazy also instantiates the object only once and then reuses it whenever needed, so isn't this similar to a @Singleton scope?
    Well, the answer is No!
    Each injected Lazy is independent, and remembers its value in isolation from other Lazy instances. This means that if we inject a second instance of ComputeLayer with Lazy it'll have a different instance, while if they are marked with @Singleton they will always return the same object in its scope.

  • Import dagger.lazy , not the lazy keyword in Kotlin.

    While both of them have the same ultimate goal of lazy initialization, but lazy in Dagger is specifically made to work with dagger dependencies while Kotlin lazy is more generic.

3. Provider Injection:

a. What is Provider Injection?

  • Provider<T> is a Dagger2 construct that allows obtaining a new instance of a dependency every time it's requested.

b. Why use Provider Injection?

  • Multiple Instances: Useful when you need a new instance of a dependency every time, rather than a singleton or scoped instance.

  • Dynamic Behavior: If the creation logic of the dependency can change dynamically, a Provider ensures you always get the latest configuration.

c. How to use Provider Injection?

Let's say that instead of using the same object of the ComputeLayer every time, what if we needed a new fresh instance of it for each call? This is exactly what Provider injection is made for.

But before we test it, we need to remove the @PerActivity scope from the ComputeLayer since it'll force the framework to create only 1 instance and reuse it whenever needed.

//@PerActivity removed the custom scope, so that object is created everytime.
public class ComputeLayer {
    String TAG = this.getClass().getCanonicalName();
    String layer = "Computation";
    NetworkLayer network;

    @Inject
    public ComputeLayer(NetworkLayer networkLayer){
        this.network = networkLayer;
        Log.e(TAG, "Compute Layer Created uses network layer as " + networkLayer);
    }
}

Now, let's go ahead and change the Lazy to the Provider construct.

class MainActivity : AppCompatActivity() {
    var calculate : Button? = null
    var TAG = this.javaClass.canonicalName

//    @Inject
//    lateinit var computation : ComputeLayer

    @Inject
    lateinit var lazyComputation: Provider<ComputeLayer>
//...everything else remains the same
}

That's it, let's run our app and see the results.

Notice that on three button clicks, we got three new fresh instances.

Remember

  1. While the ComputeLayer was created everytime we clicked the button, note that the other dependent classes, like NetworkLayer & StorageLayer were only created once.

  2. Provider respects Dagger2's scoping. If a dependency is scoped (e.g., @Singleton), even a Provider will return the same instance every time.

4. Differences between Lazy and Provider:

  • Instantiation Timing:

    • Lazy: Creates the dependency only once, the first time it's requested.

    • Provider: Creates a new instance every time it's requested.

  • Use Cases:

    • Lazy: When you want to defer the creation of a heavy or seldom-used dependency.

    • Provider: When you need multiple instances or dynamically created dependencies.

5. Considerations:

a. Scoping:

  • Both Lazy and Provider respect Dagger2's scoping. If a dependency is scoped (e.g., @Singleton), even a Provider will return the same instance every time.

b. Overhead:

  • Using Lazy and Provider introduces a slight overhead. While this is typically negligible, it's something to be aware of in performance-critical applications.

Conclusion:

Lazy and Provider injection in Dagger2 offers developer more flexibility in managing their dependencies. While standard injection is sufficient for most use cases, understanding these constructs can be beneficial in specific scenarios, ensuring efficient and organized dependency management.