EyesClear
EyesClear

Reputation: 28417

Dagger's dependency injection ignored, field remains null

I have 2 modules: AndroidModule for providing Context-related objects and PhotosModule providing PhotoManager.

I want to use PhotoManager in PhotosFragment. PhotoManager depends on DbManager, which is provided by AndroidModule.

How do I connect all the dots?

The code compiles, but a NPE is thrown on this line:

mDbManager.readDatabase();

meaning that the injection of DbManager into PhotoManager did not occur, even though I set the

injects = PhotoManager.class

in AndroidModule.

Also, mDbManager is not a private field, so I don't think I have to use injection on PhotoManager's constructor; field injection should be sufficient.

What's the reason and how do I satisfy the missing dependency? I assume there may be more than one problem with my code. Note: I just started with Dagger, please keep that in mind.

Application:

public class TestApp extends Application {

    private ObjectGraph mObjectGraph;

    @Override
    public void onCreate() {
        super.onCreate(); 
        mObjectGraph = ObjectGraph.create(getModules().toArray());
    }

    protected List<Object> getModules() {
        return Arrays.asList(new AndroidModule(this), new PhotosModule());
    }

    public void inject(Object object) {
        mObjectGraph.inject(object);
    }
}

AndroidModule:

@Module(library = true, injects = PhotoManager.class)
public class AndroidModule {

    private final TestApp mApplication;

    public AndroidModule(TestApp application) {
        mApplication = application;
    }

    @Provides
    @Singleton
    @ForApplication
    Context provideApplicationContext() {
        return mApplication;
    }

    @Provides
    @Singleton
    DbManager provideDbManager() {
        return new DbManager(mApplication);
    }
}

PhotosModule:

@Module(injects = PhotosFragment.class)
public class PhotosModule {

    @Provides
    @Singleton
    PhotoManager providePhotoManager() {
        return new PhotoManager();
    }
}

PhotoManager:

@Singleton
public class PhotoManager {

    @Inject
    DbManager mDbManager;

    public void doSomething() {
        mDbManager.readDatabase();
    }
}

PhotosFragment:

public class PhotosFragment extends Fragment {

    @Inject
    PhotoManager mPhotoManager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ((TestApp) getActivity().getApplication()).inject(this);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        ///...
        mPhotoManager.doSomething();
    }
}

DbManager:

@Singleton
public class DbManager {

    public DbManager(Context context) {
        //...
    }

    public void readDatabase() {
        //...
    }
}

Upvotes: 0

Views: 844

Answers (1)

G. Lombard
G. Lombard

Reputation: 3679

Here's the core of the problem: in PhotosModule, you have the provider method:

PhotoManager providePhotoManager() {
    return new PhotoManager();
}

You are newing up the PhotoManager yourself, so Dagger doesn't have an opportunity to inject its dependency (the DbManager).

I think it's because you're misunderstanding the meaning of the injects property on the @Module(injects=...) annotation of the AndroidModule. You have:

@Module(library = true, injects = PhotoManager.class)

But that injects isn't needed, because you're never calling objectGraph.inject on the PhotoManager. Instead, you're only injecting the PhotoFragment, which is correct...

So one way to fix the NPE problem:

  1. remove the injects=PhotoManager.class from AndroidModule.java:

    @Module(library = true)
    
  2. change PhotosModule to include AndroidModule:

    @Module(injects = PhotosFragment.class, includes = AndroidModule.class)
    
  3. I avoid field injection using the @Inject annotation (only use it for the top-level objects, i.e. where you do the objectgraph.inject, such as activity and fragment level), and use constructor injection instead for all the other dependencies. So I'd change PhotoManager to inject the DbManager like this:

    private final DbManager mDbManager;
    
    public PhotoManager(DbManager dbManager) {
        mDbManager = dbManager;
    }
    

I've put up the code here on GitHub.

Upvotes: 2

Related Questions