Search code examples
javaandroiddagger-2dagger

Android: Dagger 2 constructor injection does not call the constructor and ends up in NPE


Well, I went through all the SO posts as well as online tutorials and blogs. I can't seems to understand the reason behind the nullpointer exception in my dagger 2 constructor injection.

Problem is that constructor is not being called instead it calls,

public void getMobileDataUsage(OnDatastoreResponse onDatastoreResponse)

and causing a nullpointer

I have a singleton APIClient class which uses constructor injection.

@Singleton
public class APIClient {

private static final String TAG = "APIClient";

private APIInterface apiInterface;
private Retrofit retrofit;

@Inject
public APIClient(Context context) {
    // use 10MB cache
    long cacheSize = 10 * 1024 * 1024;
    Cache cache = new Cache(context.getCacheDir(), cacheSize);

    HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
    interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
    OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).cache(cache).build();

    retrofit = new Retrofit.Builder()
            .baseUrl(BuildConfig.BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(client)
            .build();

    this.apiInterface = retrofit.create(APIInterface.class);
}

public void getMobileDataUsage(OnDatastoreResponse onDatastoreResponse) {
    String resourceId = "a807b7ab-6cad-4aa6-87d0-e283a7353a0f";
    Integer limit = null;

    Single<DatastoreResponse> datastoreResponse = apiInterface.getMobileDataUsage(resourceId, limit);
    datastoreResponse.subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new DisposableSingleObserver<DatastoreResponse>() {
                @Override
                public void onSuccess(DatastoreResponse datastoreResponse) {
                    if (datastoreResponse.getSuccess()) {
                        Log.d(TAG, "onSuccess: " + datastoreResponse.getSuccess());

                        onDatastoreResponse.onSuccessDatastoreResponse(datastoreResponse);

                    } else {
                        Log.e(TAG, "onSuccess: " + datastoreResponse.getSuccess());
                        onDatastoreResponse.onErrorResponse(new Exception("Datastore response not successful"));
                    }
                }

                @Override
                public void onError(Throwable e) {
                    Log.e(TAG, "onError: " + e.getMessage(), e);
                    onDatastoreResponse.onErrorResponse(e);
                }
            });
}

}

I have a provider to provide Context to the above constructor injection.

@Module
public class ApplicationContextModule {

private final Context context;

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

@Provides
Context provideApplicationContext() {
    return context;
}

}

Following is my ApplicationComponent,

@Singleton
@Component(modules = {ApplicationContextModule.class, DataModule.class})
public interface ApplicationComponent {

void inject(MobileDataUsageActivity mobileDataUsageActivity);

APIClient apiClient();

Context context();

}

My Application class which builds the component,

public class MyApplication extends Application {

private ApplicationComponent applicationComponent;

@Override
public void onCreate() {
    super.onCreate();

    applicationComponent = DaggerApplicationComponent
            .builder()
            .applicationContextModule(new ApplicationContextModule(this))
            .dataModule(new DataModule())
            .build();

}

public ApplicationComponent getApplicationComponent() {
    return applicationComponent;
}
}

And I inject instance during onCreate of my activity,

((MyApplication) getApplication()).getApplicationComponent().inject(this);

Finally my repository class which throws the nullpointer exception. Note I have @Inject APIClient. However after debugging I noticed APIClient is null as it hasn't call the constructor.

public class MobileDataRepository {

private static final String TAG = "MobileDataRepository";

@Inject
APIClient apiClient;

private List<Quarter> quarterList = new ArrayList<>();
private List<Year> yearList = new ArrayList<>();

private MutableLiveData<List<Year>> mutableYearList = new MutableLiveData<>();

public LiveData<List<Year>> getYearlyMobileDataUsage() {
    apiClient.getMobileDataUsage(new OnDatastoreResponse() {
        @Override
        public void onSuccessDatastoreResponse(DatastoreResponse datastoreResponse) {

            for (QuarterResponse q : datastoreResponse.getResult().getRecords()) {
                Log.d(TAG, "Quarter: " + q.get_id() + " : " + q.getQuarter());

                String quarterInfo[] = q.getQuarter().split("-");
                String year = quarterInfo[0];
                String quarterName = quarterInfo[1];

                quarterList.add(new Quarter(q.get_id(), q.getVolume_of_mobile_data(), Integer.parseInt(year), quarterName));
            }
            mutableYearList.setValue(yearList);
        }

        @Override
        public void onErrorResponse(Throwable e) {

        }
    });

    return mutableYearList;

}

}

And the exception that tells APIClient instance was not created (Note: I have debugged to verified APIClient was null),

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.channa.mobiledatausageapp/com.channa.mobiledatausageapp.view.MobileDataUsageActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void com.channa.mobiledatausageapp.network.APIClient.getMobileDataUsage(com.channa.mobiledatausageapp.network.action.OnDatastoreResponse)' on a null object reference

Sorry for the bulky code. I just wanted to point out that I have done everything required but for some strange reason constructor DI does not work. I even tried by using a @Provider for APIClient still the error was same. Thanks in advance!

Oh well I'm using dagger version: 2.15

// Dagger 2
implementation "com.google.dagger:dagger:$rootProject.daggerVersion"
annotationProcessor "com.google.dagger:dagger-compiler:$rootProject.daggerVersion"

Solution

  • To make this injection you must create a method void inject(MobileDataRepository mobileRepository) inside your ApplicationComponent, then obtain a reference to this component inside your MobileDataRepository and call this inject method somewhere(for example, in constructor)

    Or, to make it better, as you already have an injection method to inject dependencies into your MobileDataUsageActivity, you can just create an @Inject-annotated constructor in your MobileDataRepository and then inject it to your activity. It will look like this:

    class MobileDataRepository { 
        @Inject
        public MobileDataRepository(APIClient apiClient) 
        { //do your initialization } 
    }
    

    And then, inside your activity:

    class MobileDataUsageActivity {
    
        @Inject MobileDataRepository mobileDataRepository
    
    // other code 
    }
    

    P.S. sorry for the formatting, I've written it using a phone :)