Dagger 2 - unable to inject object

5.3k views Asked by At

Im trying to do a very simple dependency injection in a Android app. I am using dagger 2 as a DI tool.

The issue is no injection is occuring:

here is my code:

//behold Motor.java in all its awe.

public class Motor {

    private int rpm;

    public Motor(){
        this.rpm = 10; //default will be 10
    }

    public int getRpm(){
        return rpm;
    }

    public void accelerate(int value){
        rpm = rpm + value;
    }

    public void brake(){
        rpm = 0;
    }
}

and here is the Vehicle.java which utilities the motor class:

import javax.inject.Inject;


public class Vehicle {

    private Motor motor;

    @Inject
    public Vehicle(Motor motor){
        this.motor = motor;
    }

    public void increaseSpeed(int value){
        motor.accelerate(value);
    }

    public void stop(){
        motor.brake();
    }

    public int getSpeed(){
        return motor.getRpm();
    }
}

I then created a VehicleModule.java class to define my provider:

import javax.inject.Singleton;

import dagger.Module;
import dagger.Provides;



@Module
public class VehicleModule {

    @Provides
    @Singleton
    Motor provideMotor(){
        return new Motor();
    }

    @Provides @Singleton
    Vehicle provideVehicle(){
        return new Vehicle(new Motor());
    }
}

I then i have a interface component annotated, defined like this:

import javax.inject.Singleton;

import Modules.VehicleModule;
import dagger.Component;

@Singleton
@Component(modules = {VehicleModule.class})
public interface VehicleComponent {

    Vehicle provideVehicle();

}

and here is my Android mainactivity class that should be injected but its not, can anyone help:

import javax.inject.Inject;

public class MainActivity extends ActionBarActivity {

    @Inject
    Vehicle vehicle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        Toast.makeText(this, String.valueOf(vehicle.getSpeed()), Toast.LENGTH_SHORT).show();
    }
}

im getting a null pointer exception on vehicle:

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int com.example.uen229.myapplication.Vehicle.getSpeed()' on a null object reference

by the way my gradle dependencies look like this:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.google.dagger:dagger:2.0'
}
2

There are 2 answers

3
nhaarman On BEST ANSWER

here is my Android mainactivity class that should be injected but its not

You're expecting magical things happen when you annotate something with @Inject. While magical things will happen, it's not that magical. You will need to do that yourself, by instantiating the component implementations that Dagger generated.

You can do this in a couple of ways, I will describe two.


First, in your MainActivity's onCreate:

private Vehicle vehicle; // Note, no @Inject annotation

public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  VehicleComponent vehicleComponent = DaggerVehicleComponent.create();
  this.vehicle = vehicleComponent.provideVehicle();

  Toast.makeText(this, String.valueOf(vehicle.getSpeed()), Toast.LENGTH_SHORT).show();
}

In this case, you create an instance of VehicleComponent, implemented by Dagger, and fetch the Vehicle instance from it. The vehicle field is not annotated by @Inject. This has the advantage that the field can be private, which is a good thing to want.


Secondly, if you do want Dagger to inject your fields, you need to add an inject method to your VehicleComponent:

@Singleton
@Component(modules = {VehicleModule.class})
public interface VehicleComponent {

  Vehicle provideVehicle();

  void inject(MainActivity mainActivity);
}

In your MainActivity class, you call inject(this), which will fill the vehicle field:

@Inject
Vehicle vehicle; // Note, package-local

public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  VehicleComponent vehicleComponent = DaggerVehicleComponent.create();
  vehicleComponent.inject(this);

  Toast.makeText(this, String.valueOf(vehicle.getSpeed()), Toast.LENGTH_SHORT).show();
}

This brings a bit of extra configuration, but is sometimes necessary. I like the first method however.


As a final comment, let's have a look at your VehicleModule, and really use the power of Dagger.

Instead of using the module to create the instances yourself, you can make Dagger to that for you. You've already annotated the Vehicle constructor with @Inject, so Dagger will know to use this constructor. However, it needs an instance of Motor, which it doesn't know of. If you add an @Inject annotation to the constructor of Motor as well, and annotate the Motor class with @Singleton, you can get rid of the VehicleModule altogether!

For example:

@Singleton
public class Motor {

    private int rpm;

    @Inject // Just an annotation to let Dagger know how to retrieve an instance of Motor.
    public Motor(){
        this.rpm = 10; //default will be 10
    }

    public int getRpm(){
        return rpm;
    }

    public void accelerate(int value){
        rpm = rpm + value;
    }

    public void brake(){
        rpm = 0;
    }
}

Your Vehicle class:

@Singleton
public class Vehicle {

    private Motor motor;

    @Inject
    public Vehicle(Motor motor){
        this.motor = motor;
    }

    public void increaseSpeed(int value){
        motor.accelerate(value);
    }

    public void stop(){
        motor.brake();
    }

    public int getSpeed(){
        return motor.getRpm();
    }
}

You can now safely delete the VehicleModule class, and remove the reference to it in your VehicleComponent.

0
rahul.ramanujam On

you are missing the following steps

you have create the module like this in application class

public class D2EApplication extends Applicaiton {
    public static D2EComponent component(Context context) {
            return ((D2EBaseApplication) context.getApplicationContext()).component;
        }

        public final static class DaggerComponentInitializer {

            public static D2EComponent init(D2EBaseApplication app) {
                return DaggerD2EComponent.builder()
                        .systemServicesModule(new VehicleModule(app))
                        .build();
            }

        }
    }

and inject it to the activity

D2EApplication.component(this).inject(this);