Dependency Injection (DI) in Android: Injecting Values (Day 7)

Photo by Joan Gamell on Unsplash

Dependency Injection (DI) in Android: Injecting Values (Day 7)

Hey there! Remember when we chatted about those cool dagger injection methods in our last article? We also dived into the world of modules in Dagger2. Now, we're back at it, and this time, we're tackling a nifty challenge: passing values to objects on the fly. No more hardcoding, folks! Let's dive in and see how it's done.

The Challenge

Consider our older example, where we added a delay of a couple of seconds to the NetworkSetup & NetworkSetupSecond in the constructor. Let's assume that we'd like to pass it at runtime instead of hardcoding these values.

This would obviously change the code of our NetworkSetupSecond to this:

public class NetworkSetupSecond implements NetworkLayer{
    String TAG = this.getClass().getCanonicalName();
    StorageLayer storageLayer;

    public NetworkSetupSecond(StorageLayer storageLayer, int delay){
        this.storageLayer = storageLayer;
        try {
            Log.e(TAG, "Initialising network second and delay is " + delay );
            Thread.sleep(delay);
            Log.e(TAG,"Network Second initialization done");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Log.e(TAG, "Network Layer Second Created");
    }
//....
}

Here, we are passing the delay as an argument in the constructor. Now we can easily pass this value to the NetworkSetupSecond class from the NetworkModuleSecond class where we had created this object, as shown below.

@Module
public class NetworkModuleSecond {

    @Provides
    NetworkLayer provideNetworkSetupSecond(StorageLayer storageLayer){
        return new NetworkSetupSecond(storageLayer, 1000);
    }
}

Let's build and run this program and check the logs.

Okay, so all good so far. We are able to pass an integer to our constructor from the module.

But Wait!

We actually want to pass this value at runtime, which basically means that we'd somehow like to pass this value from the MainActivity to our dagger graph after the app has been created.

And here lies the problem, how would we pass the delay from our activity to our NetworkModuleSecond?

Fortunately, Dagger has a solution for this.

The Solution

Step 1 :

First, we need to create a constructor for our module and pass the delay as a constructor argument as shown below.

public class NetworkModuleSecond {

    int delay;

    public NetworkModuleSecond(int delay){
        this.delay = delay;
    }

    @Provides
    NetworkLayer provideNetworkSetupSecond(StorageLayer storageLayer){
        return new NetworkSetupSecond(storageLayer, delay);
    }
}

Step 2:

Don't forget to add this module to our component!

@Component (modules = NetworkModuleSecond.class)
public interface ComputeComponent {
    //annotation processing

    ComputeLayer getComputeLayer();

    void inject  (MainActivity mainActivity);
}

Step 3:

Let's build our project!

And we get an error!

It is basically telling us that we do not have the create method available anymore in our DaggerComputeComponent class.

This is because the create method is only available if none of our modules takes an argument in the constructor. The reason behind this behavior in Dagger 2 is rooted in how dependency injection works and the design principles of the Dagger framework.

When a module requires arguments in its constructor, it indicates that there's some external dependency or configuration that Dagger cannot automatically resolve. Dagger doesn't make assumptions about how to provide these external values. Instead, it requires the developer to explicitly provide them.

So, let's get go ahead and provide these external values to our dagger graph from the MainActivity.

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

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

        //Notice how the create method is chaned with a builder method instead

        val component = DaggerComputeComponent.builder().
        networkModuleSecond(NetworkModuleSecond(1000)).build()
        component.inject(this)

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

What we've done here, is that instead of letting Dagger2 create the NetworkModuleSecond class, we created it on our own and passed the delay value as an argument.

And that's it! We have successfully injected a value to an object in our dagger graph at runtime!

Conclusion

Alright, we've had a good run exploring how to inject values into objects at runtime with Dagger2. But guess what? We're just scratching the surface here. Stick around for part 2 of this blog post, where we'll dive even deeper and have some more fun with Dagger2. See you there!