Dagger2+Retrofit change URL with Subcomponent : cannot be provided without an @Provides- or @Produces-annotated method

hamidreza haajhoseini :

I'm newbie in Dagger .I've used this answer to change URL at Runtime.

Also I've used three modules and three components as shown below:

note : I have tow Components (ApplicationComponent,ActivityComponent) and one Subcomponent(UrlComponent) In additation I use @Singletone,@PerUrl and @PerActivty as a Scope.

When I want to Inject RestApi into every Activity I encounter this Error :

 error: com.example.testdagger2.RestApi cannot be provided without an @Provides- or @Produces-annotated method.
com.example.dagger2.RestApi is injected at
com.example.dagger2.RestApiHelper.restApi
com.example.dagger2.RestApiHelper is injected at
com.example.dagger2.MainActivity.restApiHelper
com.example.testdagger2.MainActivity is injected at
com.example.testdagger2.di.component.ActivityComponent.inject(mainActivity)

Before I had to change the URL in Runtime, I had two Component and modules (appCompoent and activtyComponent), and all the Providers (like Retrofit and RestApi ,...) were in the applicationModule and the program worked fine.

ApplicationComponent.java

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

    void inject(ExampleApplication exampleApplication);

    @ApplicationContext
    Context context();

    Application application();

    UrlComponent plus(UrlModule component);
}

ApplicationModule.java

@Module
public class ApplicationModule {

    private Application mApplication;

    public ApplicationModule(Application application) {
        this.mApplication = application;
    }

        @Provides
        @ApplicationContext
        Context provideContext() {
            return mApplication;
        }

        @Provides
        Application provideApplication() {
            return mApplication;
        }
    }

UrlComponent.java

@PerUrl
@Subcomponent(modules = UrlModule.class)
public interface UrlComponent {

}

UrlModule.java

@Module
public class UrlModule {
    private String url;

    public UrlModule(String url) {
        this.url = url;
    }

    @Provides
    @PerUrl
    OkHttpClient provideOkhttpClient() {
        return new OkHttpClient.Builder()
                .connectTimeout(20, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .build();
    }

    @Provides
    @PerUrl
    Retrofit provideRetrofit(String baseURL, OkHttpClient client) {

        return new Retrofit.Builder()
                .baseUrl(baseURL)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .client(client)
                .build();
    }

    @Provides
    RestApiHelper provideRestApiHelper(RestApiHelper restApiManager) {
        return restApiManager;
    }

    @Provides
    public RestApi provideApiService() {
        return provideRetrofit(url, provideOkhttpClient())
                .create(RestApi.class);

    }
}

ActivityComponent.java

@PerActivity
@Component(modules = ActivityModule.class,dependencies = ApplicationComponent.class)
public interface ActivityComponent {

    void inject(MainActivity mainActivity);
  .
  .
  .

}

ActivityModule.java

@Module
public class ActivityModule {

    private AppCompatActivity mActivity;

    public ActivityModule(AppCompatActivity mActivity) {
        this.mActivity = mActivity;
    }


    @Provides
    @ActivityContext
    Context provideContext() {
        return mActivity;
    }

    @Provides
    AppCompatActivity provideActivity() {
        return mActivity;
    }

   .
   .
   .

}

RestApi.java

public interface RestApi {

    Single<Object> getquestionlist(@Query("page") int page);

    Single<Object> getCategoryList();
}

RestApiHelper.java

public class RestApiHelper implements RestApi {

    @Inject
    RestApi restApi;

    @Inject
    public RestApiHelper(RestApi restApi) {
        this.restApi = restApi;
    }

    @Override
    public Single<Object> getquestionlist(int page) {
        return restApi.getquestionlist(1);
    }

    @Override
    public Single<Object> getCategoryList() {
        return restApi.getCategoryList();
    }

ExampleApplication.java

public class ExampleApplication extends Application {


    @Inject
    ApplicationComponent appComponent;


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

        appComponent = DaggerApplicationComponent.builder()
                .applicationModule(new ApplicationModule(this)).build();
        appComponent.inject(this);
        appComponent.plus(new UrlModule("www/test/en"));


    }

    public ApplicationComponent getAppComponent() {
        return appComponent;
    }


}

BaseActivity.java

public class BaseActivity extends AppCompatActivity {


    ActivityComponent activityComponent;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //activityComponent=DaggerActivityComponent.builder().applicationModule()

        activityComponent= DaggerActivityComponent.builder()
                .applicationComponent(((ExampleApplication)getApplication()).getAppComponent())
                .build();

    }

    public ActivityComponent getActivityComponent() {
        return activityComponent;
    }
}

MainActivity.jaqva

public class MainActivity extends BaseActivity {


    @Inject
    RestApiHelper restApiHelper;

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


    ActivityComponent component= getActivityComponent();
    component.inject(this);

        restApiHelper.getquestionlist(1).subscribe(new Consumer<Object>() {
        @Override
        public void accept(Object o) throws Exception {

        }
    });

Now I have tow questions :

1-Shouldn't all the Providers built into the Urlmodule be added to ApplicationModule(UrlModule is Subcomepoent of the ApplicationComponent )

 @PerActivity
    @Component(modules = ActivityModule.class,dependencies = ApplicationComponent.class)
    public interface ActivityComponent {

and since the ApplicationComponent is an ActivityComponent dependencies, can all of these Providers be used in the ActivityComponent as well?

2-And as a basic question, where is the problem?

Pavneet_Singh :

Shouldn't all the Providers built into the Urlmodule be added to ApplicationModule(UrlModule is Subcomepoent of the ApplicationComponent )

When you declare a component dependancy then the dependant component can only use the exposed dependencies which are declared inside the component class of dependencies, in your case these are context and application in your ApplicationComponent class and provided by ApplicationModule.

For validation(testing):

1) Add a new provides in your app module

// inside ApplicationModule
@Provides
Student getNum() {
    return new Student("aa");
}

2) Create and use ActivityComponent object to inject the Student in any class. It will result in missing provides. It can be fixed by exposing Student in ApplicationComponent class as:

Student getStu();

2-And as a basic question, where is the problem?

With the following implementations:

  1. Dagger misconfigured dependancy graph, as explained above
  2. Missing Rxjava implementation and retrofit implementations
  3. Missing url provides etc

To fix the above follow the below steps:

  1. Dagger misconfigured dependancy graph, as explained above

a) MainActivity requires the RestApiHelper which is provided by UrlComponent not AppComponent so first use UrlComponent as dependencies instead of AppComponent

@PerActivity
@Component(modules = ActivityModule.class, dependencies = {UrlComponent.class})
public interface ActivityComponent {

    void inject(MainActivity mainActivity);

}

b) Now expose the dependancy in the UrlComponent

@PerUrl
@Subcomponent(modules = UrlModule.class)
public interface UrlComponent {
    RestApiHelper getRetrofit();
}

Implementation of UrlModule.class with fixed issues(mentioned above)

import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import java.util.concurrent.TimeUnit;    
import dagger.Module;
import dagger.Provides;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

@Module
public class UrlModule {
    private String url;

    public UrlModule(String url) {
        this.url = url;
    }

    @Provides
    @PerUrl
    OkHttpClient provideOkhttpClient() {
        return new OkHttpClient.Builder()
                .connectTimeout(20, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .build();
    }

    @Provides
    String provideUrl() {
        return url;
    }

    @Provides
    @PerUrl
    Retrofit provideRetrofit(String baseURL, OkHttpClient client) {

        return new Retrofit.Builder()
                .baseUrl(baseURL)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .client(client)
                .build();
    }


    @Provides
    RestApi provideApiService(Retrofit retrofit) {
        return retrofit.create(RestApi.class);
    }
}

c) Build Url appComponent and urlComponent in Application class for later use as

public class ExampleApplication extends Application {

    // note, I remove the inject, you were doing it for testing etc
    private ApplicationComponent appComponent;

    private UrlComponent urlComponent;

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

        appComponent = DaggerApplicationComponent.builder()
                .applicationModule(new ApplicationModule(this)).build();

        urlComponent = appComponent.plus(new UrlModule("https://jsonplaceholder.typicode.com/"));


    }

    public ApplicationComponent getAppComponent() {
        return appComponent;
    }

    public UrlComponent getUrlComponent() {
        return urlComponent;
    }


}

Now build the activityComponent in BaseActivity as

activityComponent= DaggerActivityComponent.builder()
                .activityModule(new ActivityModule(this))
                .urlComponent(((ExampleApplication)getApplication()).getUrlComponent())
                .build();

and use it in your MainActivity as

public class MainActivity extends BaseActivity {


    @Inject
    RestApiHelper restApiHelper;
    Disposable disposable;
    private static final String TAG = "MainActivity";

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


        ActivityComponent component = getActivityComponent();
        component.inject(this);

        disposable = restApiHelper.getquestionlist(1)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Object>() {
                    @Override
                    public void accept(Object o) throws Exception {
                        Log.d(TAG, "accept: "+ o.toString());    
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        throwable.printStackTrace();
                    }
                });
    }

    @Override
    protected void onDestroy() {
        disposable.dispose(); // best practice
        super.onDestroy();
    }
}

That's it.

Note: Make sure your baseUrl value and retrofit objects are properly setup and have internet app permission etc. You can optimised your code further with lambdas etc as I kept most of the code as it is for understanding.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=420484&siteId=1