Lazy and Provider Injection in Dagger2 : Day 13
"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.
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.
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 injectedLazy
is independent, and remembers its value in isolation from otherLazy
instances. This means that if we inject a second instance ofComputeLayer
withLazy
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
While the
ComputeLayer
was created everytime we clicked the button, note that the other dependent classes, likeNetworkLayer
&StorageLayer
were only created once.Provider
respects Dagger2's scoping. If a dependency is scoped (e.g.,@Singleton
), even aProvider
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
andProvider
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
andProvider
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.
Subscribe to my newsletter
Read articles from Abou Zuhayr directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Abou Zuhayr
Abou Zuhayr
Hey, I’m Abou Zuhayr, Android developer by day, wannabe rockstar by night. With 6 years of Android wizardry under my belt and currently working at Gather.ai, I've successfully convinced multiple devices to do my bidding while pretending I'm not secretly just turning them off and on again. I’m also deeply embedded (pun intended) in systems that make you question if they’re alive. When I'm not fixing bugs that aren’t my fault (I swear!), I’m serenading my code with guitar riffs or drumming away the frustration of yet another NullPointerException. Oh, and I write sometimes – mostly about how Android development feels like extreme sport mixed with jazz improvisation. Looking for new challenges in tech that don’t involve my devices plotting against me!