Reputation: 454
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?
Upvotes: 1
Views: 320
Reputation: 37404
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:
Rxjava
implementation and retrofit implementationsTo fix the above follow the below steps:
- 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.
Upvotes: 2
Reputation: 16729
First of all your method that provides RestApi is wrong. I should be like
@Provides
public RestApi provideApiService(Retrofit retrofit) {
return retrofit.create(RestApi.class);
}
Upvotes: 0