I'm trying to learn dagger 2 but I'm confused in injecting of constructor with interface. This is my below code :
MainActivity.java
public class MainActivity extends AppCompatActivity implements MainView {
// this keyword of request dependency . At compiling process, dagger will look at all of these annotations
//to create the exact dependency
#Inject MainPresenter mainPresenter ;
TextView textView ;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textview) ;
DaggerPresenterComponent.create().inject(this);
textView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
mainPresenter.doThings(8555) ;
}
});
}
/**********************************/
#Override
public void invokeRandomViewMethod(String msg) {
textView.setText(msg);
}
}
MainPresenter.java
public class MainPresenter {
private MainView mainView ;
#Inject
public MainPresenter(MainView mainView) {
this.mainView = mainView;
}
public void doThings(int value){
Random random = new Random();
int rand= random.nextInt(value) ;
if(mainView != null){
mainView.invokeRandomViewMethod("You random number is "+rand);
}
}
public interface MainView {
void invokeRandomViewMethod(String msg) ;
}
}
This is the Module :
#Module
public class PresenterModule {
#Provides
// this is the method that will provide the dependancy
MainPresenter provideMainPresenter(MainView mainView){
return new MainPresenter(mainView);
}
}
And this is the Component
#Component (modules = PresenterModule.class)
public interface PresenterComponent {
void inject(MainActivity activity) ;
}
When I run the code it shows me this error :
Error:(15, 10) error: com.imennmn.hellodagger2example.MainView cannot
be provided without an #Provides-annotated method.
com.imennmn.hellodagger2example.MainView is injected at
com.imennmn.hellodagger2example.presenterInjection.PresenterModule.provideMainPresenter(mainView)
com.imennmn.hellodagger2example.MainPresenter is injected at
com.imennmn.hellodagger2example.MainActivity.mainPresenter
com.imennmn.hellodagger2example.MainActivity is injected at
com.imennmn.hellodagger2example.simpleInjection.DataComponent.inject(activity)
My Question is how I can provide the interface MainView by inject it with dagger and bind the MainPresenter and MainActivity ?
Any help would be appreciated !
By following code:
MainPresenter provideMainPresenter(MainView mainView) {
return new MainPresenter(mainView);
}
You are telling dagger: "hey, whenever I ask you to inject MainPresenter, construct it using MainView".
But dagger complaints, because you haven't specified how exactly he should build/acquire MainView.
So, in your PresenterModule do this:
#Module
public class PresenterModule {
MainView mainView;
public PresenterModule(MainView mainView) {
this.mainView = mainView;
}
#Provides
MainPresenter provideMainPresenter() {
return new MainPresenter(mainView);
}
}
Then when building the component:
DaggerPresenterComponent.builder()
.presenterModule(new PresenterModule(this))
.build();
Your provideMainPresenter implicitly depends on a MainView. Dagger has no way to get it. You need to add a method to provide it:
#Module
public class PresenterModule {
#Provides
MainView provideMainView(){
// Provide MainView here somehow so Dagger can use this to create a MainPresenter
}
#Provides
// this is the method that will provide the dependancy
MainPresenter provideMainPresenter(MainView mainView){
return new MainPresenter(mainView);
}
}
Add abstract module with #Binds annotation, look at my impl of : AbstractTestSettingsFragmentModule.java
TestFragment.java
public class TestFragment extends Fragment{
#Inject TestFragmentContract.Presenter mPresenter;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidSupportInjection.inject(this);
}
}
TestFragmentPresenterImpl.java
public class TestFragmentPresenterImpl implements TestFragmentContract.Presenter {
#Inject
public TestFragmentPresenterImpl(){
}
}
AbstractTestSettingsFragmentModule.java
#Module
public abstract class AbstractTestSettingsFragmentModule {
#Binds
#NonNull
public abstract TestSettingsFragmentContract.Presenter testSettingsFragmentPresenterImpl(TestSettingsFragmentImpl presenter);
}
ContributesModule.java
#Module
public abstract class ContributesModule {
#ContributesAndroidInjector(modules = {AbstractTestSettingsFragmentModule.class})
abstract TestSettingsFragment testSettingsFragment();
}
AppComponent.java
#Singleton
#Component(
modules = {
AndroidSupportInjectionModule.class,
ContributesModule.class,
AppModule.class,
})
public interface AppComponent extends AndroidInjector<DaggerApplication> {
void inject(TheApplication theApplication);
#Override
void inject(DaggerApplication instance);
#Component.Builder
interface Builder {
#BindsInstance
Builder application(Application application);
AppComponent build();
}
}
You can use Assisted Injection with Dagger: https://dagger.dev/dev-guide/assisted-injection.html
Assisted injection is a dependency injection (DI) pattern that is used to construct an object where some parameters may be provided by the DI framework and others must be passed in at creation time (a.k.a “assisted”) by the user.
Related
How to implement Dagger for worker classes in Dagger 2.16?
public class SyncWorker extends Worker {
#Inject
ISettingRepository settingRepository;
#NonNull
#Override
public Result doWork() {
sync();
return Result.SUCCESS;
}
private void sync() {
}
}
my AppComponent
#Singleton
#Component(modules = {
AndroidSupportInjectionModule.class,
BaseModule.class,
ApiModule.class,
UserDatabaseModule.class,
SaleDatabaseModule.class,
AppModule.class,
ActivityBuilderModule.class
})
public interface AppComponent extends AndroidInjector<DaggerApplication> {
void inject(BaseApp app);
#Override
void inject(DaggerApplication application);
#Component.Builder
interface Builder {
#BindsInstance
Builder getApp(Application application);
AppComponent build();
}
}
How can I inject the settingRepository?
I was able to inject a worker with this approach.
First, create a new method inside your app component that will inject your worker class.
#Singleton
public interface AppComponent extends AndroidInjector<YourApplicationClass> {
#Component.Builder
interface Builder {
#BindsInstance
AppComponent.Builder application(Application application);
AppComponent build();
}
void inject(NotifWorker worker);
}
Inside your application, build your dagger component as usual but assign it to a variable and make it global.
public class YourApplicationClass implements HasActivityInjector {
private AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder()
.application(this)
.build();
appComponent.inject(this);
}
public AppComponent getAppComponent() {
return appComponent;
}
}
Inside your worker class, do something like this.
public class NotifWorker extends Worker {
#Inject
ToBeInjectedClass toBeInjectedClass;
public NotifWorker() {
YourApplicationClass
.getInstance()
.getAppComponent()
.inject(this)
}
}
To inject any classes in Dagger 2, you have to first provide those classes using #provides annotation in the module.
You have to first create a component containing a module which provides the desired class.
Example:-
#component(modules = {Module1.class})
public interface Component1{
void inject(SyncWorker syncWorker);
}
#Module
public class Module1{
#Provides
public ISettingRepository getSettingRepo(){
return new ISettingRepository();
}
}
Now write in your code, a constructor that is used to inject the component into your worker class.
public class SyncWorker extends Worker {
#Inject
ISettingRepository settingRepository;
public SyncWorker(){
DaggerComponent1.builder().build().inject(this);
}
#NonNull
#Override
public Result doWork() {
sync();
return Result.SUCCESS;
}
private void sync() {
}
}
Now when the constructor runs, your settingRepository instance would be initialized.
I try to create sample application with dagger 2 using mvp & RXAndroid, every thing work correctly but I cannot able to inject Activity the following is my AppComponent
#Singleton
#Component(modules = {AppModule.class})
public interface AppComponent {
void inject(App app);
void inject(MainActivity activity);
void inject(ResponseService service);
void inject(MainPresenter presenter);
}
and the following is my Module
#Module
public class AppModule {
private App app;
public AppModule(App app) {
this.app = app;
}
private static final String API_ENDPOINT = "url here";
#Provides
#Singleton
public ApiService apiService() {
OkHttpClient client = new OkHttpClient();
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ClassTypeAdapterFactory())
.registerTypeAdapter(Class.class, new ClassTypeAdapter()).create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_ENDPOINT)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build();
return retrofit.create(ApiService.class);
}
#Provides
#Singleton
ResponseService responseService() {
return new ResponseService(app.getComponent());
}
#Provides
#Singleton
MainPresenter mainPresenter() {
return new MainPresenter(app.getComponent());
}
#Provides
#Singleton
EventBus eventBus() {
return EventBus.getDefault();
}
}
I inject all things correctly and can work with them except ManiActivity when try to use it give me null pointer the following how i inject it
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((App) getApplicationContext())
.getComponent()
.inject(this);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
initRecyclerView();
presenter.setView(this);
}
but when try to use it as Context in My adapter and pass it to Picasso library give me the following exception java.lang.IllegalArgumentException: Context must not be null.
the following is how i use it
#Inject
MainActivity activity;
and use it in onBindViewHolder as following
Picasso.with(activity).load(response).fit().into(holder.ivCover);
My adapter code
public class ReAdapter extends RecyclerView.Adapter<ReAdapter.RViewHolder> {
private List<Response> responseList;
#Inject
MainActivity appContext;
public ReAdapter() {
}
public void setResponseList(List<Response> responseList) {
this.responseList = responseList;
notifyDataSetChanged();
}
#Override
public RViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.response_item, parent, false);
return new RViewHolder(view);
}
#Override
public void onBindViewHolder(RViewHolder holder, int position) {
final Response response = responseList.get(position);
Picasso.with(appContext).load(response.getValue().toLowerCase()).fit().into(holder.ivd);
}
#Override
public int getItemCount() {
return responseList != null ? responseList.size() : 0;
}
public class RViewHolder extends RecyclerView.ViewHolder {
#BindView(R.id.ivd)
ImageView ivd;
public RViewHolder(View view) {
super(view);
ButterKnife.bind(this, itemView);
}
}
}
Can anyone help me to solve this issue ?
Your activity: MainActivity doesn't have an #Inject annotated constructor (and can't have, because it's created by the system).
In your AppModule there is no #Provides annotated method that returns MainActivity.
You are injecting the fields in your activity not in your adapter.
The result is Dagger has no idea how to create an object of type MainActivity.
The solution for this specific problem is to use the application context for Picasso or even better create a Picasso object with Dagger.
Update your AppModule with:
#Provides
#Singleton
Picasso providePicasso(App app) {
return Picasso.with(app);
}
In MainActivity add a field:
#Inject
ReAdapter adapter;
In ReAdapter modify the constructor and add a field for picasso:
private final Picasso picasso;
#Inject
public ReAdapter(Picasso picasso) {
this.picasso = picasso;
}
This way Dagger can create a singleton Picasso instance. The ReAdapter is annotated with #Inject (using constructor injection) so Dagger knows how to create it. By adding a ReAdapter field in MainActivity when you call component.inject(this) in MainActivity the adapter field will be initialized.
I want to provide Activity/Fragment Context to general module, which can be used throughout the application.
Sample of module, which depends on context. Toast can be invoked from AppContext, but in this sample lets pretend we need Activity context:
public class ToastController {
private Context context;
#Inject
public ToastController(Context context) {
this.context = context;
}
public void showToast(#StringRes int res) {
Toast.makeText(context, context.getText(res), Toast.LENGTH_SHORT).show();;
}
}
I created abstract Activity which extends every Activity I use in project and through this Activity i want to provide Context using its provider to all Activities.
public abstract class CoreAppActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(getContentLayout());
ButterKnife.bind(this);
}
protected abstract #LayoutRes int getContentLayout();
}
so I created Activity and injected ToastCotroller. Context should be provided from abstract CoreAppActivity but it is not.
public class MainTabActivity extends CoreAppActivity {
#Inject
ToastController toastController;
#Override
public int getContentLayout() {
return R.layout.activity_main_tab;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
...
}
Here is my module Components and modules I used to provide dependencies.
#Singleton
#Component(modules = {
AndroidInjectionModule.class,
AppModule.class,
MainTabActivityModule.class, //Here I added all module dependencies
})
public interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
Builder application(Application application);
AppComponent build();
}
void inject(MyApplication myApplication);
}
MainTabActivityModule in this class I added Activities ant it is also the class where CoreAppActivity is added and also has module which exposes its context.
#Module
public abstract class MainTabActivityModule {
#ContributesAndroidInjector
abstract MainTabActivity contributeMainActivity();
#ContributesAndroidInjector(modules = ContextCoreActivityModule.class)
abstract CoreAppActivity contributeCoreAppActivity();
}
Module ContextCoreActivityModule binds CoreAppActivity with its context
#Module
public abstract class ContextCoreActivityModule {
#Binds
abstract Context provideContext(CoreAppActivity featureActivity);
}
However Dagger throws error by compilation.
Error:(33, 10) error: The same map key is bound more than once for java.util.Map<java.lang.Class<? extends android.app.Activity>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.app.Activity>>>
#Binds #dagger.multibindings.IntoMap #dagger.android.ActivityKey(system.malbac.sk.attandancesystem.activities.CoreAppActivity.class) dagger.android.AndroidInjector.Factory<? extends android.app.Activity> system.malbac.sk.attandancesystem.di.modules.MainTabActivityModule_ContributeCoreAppActivity.bindAndroidInjectorFactory(system.malbac.sk.attandancesystem.di.modules.MainTabActivityModule_ContributeCoreAppActivity.CoreAppActivitySubcomponent.Builder)
#Binds #dagger.multibindings.IntoMap #dagger.android.ActivityKey(system.malbac.sk.attandancesystem.activities.CoreAppActivity.class) dagger.android.AndroidInjector.Factory<? extends android.app.Activity> system.malbac.sk.attandancesystem.di.modules.CoreActivityModule.bindYourActivityInjectorFactory(system.malbac.sk.attandancesystem.di.components.CoreActivitySubComponent.Builder)
Why does the below code fail to compile with the following error, and what should be done to fix it?
Error:(9, 8) error: [SubComponent.inject(MainActivity)] java.lang.Integer cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
java.lang.Integer is injected at
MainActivity.abc
MainActivity is injected at
SubComponent.inject(activity)
TL;DR: I am trying to create a subcomponent with a different scope than the parent component, and inject a dependency from the subcomponent into an activity.
App.java
public class App extends Application {
private AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.create();
appComponent.inject(this);
if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
}
}
public AppComponent getAppComponent() {
return appComponent;
}
public static App app(Context context) {
return (App) context.getApplicationContext();
}
}
AppComponent.java
#Singleton
#Component
public interface AppComponent {
void inject(App app);
SubComponent.Builder subComponent();
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Inject
int abc;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
App.app(this).getAppComponent()
.subComponent()
.userModule(new SubModule())
.build()
.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
}
}
SubComponent.java
#SubScope
#Subcomponent(modules = {SubModule.class})
public interface SubComponent {
void inject(MainActivity activity);
#Subcomponent.Builder
interface Builder {
Builder userModule(SubModule module);
SubComponent build();
}
}
SubModule.java
#Module
public class SubModule {
#Provides
#SubScope
public int provideAbc() {
return 1;
}
}
SubScope.java
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
public #interface SubScope {
}
To make it work, I just had to change #Qualifier to #Scope in SubScope:
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface SubScope {
}
1 - Scope:
#Scope
#Retention(RetentionPolicy.CLASS)
public #interface PerInstance {}
2 - AppContextModule:
#Module
public class AppContextModule {
Application application;
public AppContextModule(Application application){
this.application = application;
}
#Provides
public Application application(){
return this.application;
}
#Provides
public Context context(){
return this.application;
}
#Provides
public LocationManager locationManager(Context context){
return (LocationManager) context.getSystemService(context.LOCATION_SERVICE);
}
}
3 - MeuPrimeiroModule:
#Module
public class MeuPrimeiroModule {
#Provides
#PerInstance
public String nome() {
return new String("Gorick");
}
}
4 - MeuSegundoModule:
#Module(includes = MeuPrimeiroModule.class)
public class MeuSegundoModule {
#Provides
#Singleton
public String nomeCompleto(MeuPrimeiroModule meuPrimeiroModule) {
return new String(meuPrimeiroModule + " Silva");
}
}
5 - MeuPrimeiroComponent:
#PerInstance
#Component(modules={MeuPrimeiroModule.class})
public interface MeuPrimeiroComponent {
void inject(MainActivity mainActivity);
}
6 - MeuSegundoComponent:
#Singleton
#Component(modules={MeuSegundoComponent.class})
public interface MeuSegundoComponent extends MeuPrimeiroComponent {
void inject(MainActivity mainActivity);
}
7 - AppContextComponent:
public interface AppContextComponent {
Application app(); //provision method
Context applicationContext(); //provision method
LocationManager locationManager(); //provision method
}
8 - ApplicationComponent:
#Singleton
#Component(modules={AppContextModule.class})
public interface ApplicationComponent extends AppContextComponent {
void inject(MainActivity mainActivity);
}
9 - MainActivity:
public class MainActivity extends AppCompatActivity {
#Inject
MeuPrimeiroComponent meuPrimeiroComponent;
#Inject
MeuSegundoComponent meuSegundoComponent;
TextView nome, nomeCompleto;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nome = (TextView) findViewById(R.id.nome);
nomeCompleto = (TextView) findViewById(R.id.nomeCompleto);
setNome(nome, nomeCompleto);
}
public void setNome(TextView nome, TextView nomeCompleto){
nome.setText(meuPrimeiroComponent.toString());
nomeCompleto.setText(meuSegundoComponent.toString());
}
}
Build:
Error:(16, 10) error: gorick.dagger2.Dagger2.Component.MeuPrimeiroComponent cannot be provided without an #Provides- or #Produces-annotated method.
gorick.dagger2.Dagger2.Component.MeuPrimeiroComponent is injected at
gorick.dagger2.MainActivity.meuPrimeiroComponent
gorick.dagger2.MainActivity is injected at
gorick.dagger2.Dagger2.Component.ApplicationComponent.inject(mainActivity)
PS: If i use meuPrimeiroComponent.nome(), android studio doesn't find the nome() method.
You cannot inject values in component instead you need to inject in object whose constructor contains values which is returns by the providers
For more info Ref this Example
You need build Component like this on onCreate():
DaggerMeuPrimeiroComponent.builder()
// list of modules that are part of this component need to be created here too
.appContextModule(new AppContextModule(getApplicationContext())) // This also corresponds to the name of your module: %component_name%Module
.build().inject(this);