Why is my field `null` after injection? How do I i

2020-01-27 07:59发布

This is a Canonical Question because there are a lot of misconceptions about object initialization with Dagger 2.

If your question was flagged as a duplicate please read this post carefully and make sure to understand the difference between constructor injection and field injection.

I try to inject a Context into my presenter, but I get a NullPointerException when trying to use it.

class MyPresenter {

  @Inject Context context;

  private MyView view;

  @Inject
  MyPresenter(MyView view) {
    this.view = view;
  }
}

My module looks like this

@Module
class MyModule {

  @Provides
  MyPresenter provideMyPresenter(MyView view) {
    return new MyPresenter(view);
  }
}

I inject the presenter in my Activity here:

class MyActivity extends Activity {

  @Inject MyPresenter presenter;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    createMyActivityComponent().inject(this);
  }
}

2条回答
欢心
2楼-- · 2020-01-27 08:24

The above includes both constructor and field injection, but neither done right. The example would behave the same if we removed all the @Inject annotations from MyPresenter since we're not using any of them.

@Provides
MyPresenter provideMyPresenter(MyView view) {
  // no constructor injection, we create the object ourselves!
  return new MyPresenter(view);
}

// also no mention anywhere of component.inject(presenter)
// so the fields won't be injected either

Make sure to use either constructor injection or field injection. Mixing both will usually indicate an error in your setup or understanding.

  • @Inject on a field is a marker for field injection
  • @Inject on a constructor is a marker for constructor injection

This means your class should have either of

  • a single @Inject on the constructor, or
  • a @Inject on all the fields to initialize, but none on the constructor!

Don't sprinkle @Inject everywhere and expect things to work! Make sure to place the annotation where needed. Don't mix field and constructor injection!

Constructor injection should be favored over field injection as it creates an initialized and usable object. Field injection is to be used with Framework components where the Framework creates the objects. You have to manually call component.inject(object) for field injection to be performed, or any annotated fields will be null when you try to use them.

Constructor Injection

As the name suggests you put your dependencies as parameters in the constructor. The annotation on the constructor tells Dagger about the object and it can then create the object for you by calling it with all the required dependencies. Dagger will also inject any annotated fields or methods after creating the object, but plain constructor injection should usually be favored as it doesn't hide any dependencies.

Dagger creating the object also means there is no need for a @Provides method in your module that creates the object. All you need to do is add @Inject to the constructor and declare the dependencies.

class MyPresenter {

  private Context context;
  private MyView view;

  @Inject
  MyPresenter(MyView view, Context context) {
    this.view = view;
    this.context = context
  }
}

If you want to bind your implementation to an interface, there is still no need to create the object yourself.

@Module class MyModule {

  @Provides
  MyPresenter providePresenter(MyPresenterImpl presenter) {
    // Dagger creates the object, we return it as a binding for the interface!
    return presenter;
  }
}

And there is even a shorter (and more performant) version of the above use-case:

@Module interface MyModule {

  @Binds
  MyPresenter providePresenter(MyPresenterImpl presenter)
}

Constructor injection should be your default way of using Dagger. Make sure that you don't call new yourself or you misunderstood the concept.

Field Injection

There are times when you can't use constructor injection, e.g. an Activity in Android gets created by the Framework and you shouldn't override the constructor. In this case we can use field injection.

To use field injection you annotate all the fields that you want initialized with @Inject and add a void inject(MyActivity activity) method to the component that should handle the injection.

@Component
interface MyComponent {
  void inject(MyActivity activity);
}

And somewhere in your code you have to call component.inject(myActivity) or the fields will not be initialized. e.g. in onCreate(..)

void onCreate(..) {
  // fields still null / uninitialized
  myComponent.inject(this);
  // fields are now injected!

  // ...
}

Field injection is not transitive. Just because you inject an Activity this does not mean that Dagger will also inject the fields of the presenter it injected. You have to inject every object manually, which is one reason why you should favor constructor injection.

There are tools that help mitigate the boilerplate of creating components and injecting your objects like AndroidInjection.inject() which will do this for you, but it still has to be done. Another example is AppInjector which adds various lifecycle listeners to inject your Activities and Fragments, but it will still call AndroidInjection which then creates your component and injects the object.

Make sure that you inject the object before using it and that there is no constructor annotated with @Inject to avoid confusion.

What else?

There is also the lesser used method injection and of course Dagger can't inject third party libraries, which you have to construct and provide in your modules.

查看更多
Evening l夕情丶
3楼-- · 2020-01-27 08:41

Remove @Inject from the Context and create a separate module for providing Context dependency

@Module 
public class ContextModule {
    private final Context context;

    public ContextModule(Context context) {
        this.context = context;
    }

    @Provides
    @MyAppScope
    public Context getContext() {
        return context;
    }
}

then create your DaggerComponent. (I have created it in Application class and this refers to ApplicationContext

component =  DaggerDaggerAppComponent.builder()
            .contextModule(new ContextModule(this))
            .MyModule()
            .build();

You can skip .MyModule() if you want because unlike Context module it has no external dependency.

查看更多
登录 后发表回答