Dagger 2 multiple Injection on Android - android

I have an Activity with 2 injections. Each injected component works alone, but injecting both leads to the following errors :
Error:(12, 10) error: android.app.Fragment cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
fr.daggertest.MainActivity.fragmentB
[injected field of type: android.app.Fragment fragmentB]
Error:(12, 10) error: android.app.Application cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
fr.daggertest.MainActivity.applicationA
[injected field of type: android.app.Application applicationA]
But they both are already #Provides annotated, so I don't see what's wrong ?
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Inject
Fragment fragmentB;
#Inject
Application applicationA;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// create component & inject...
}
Modules & components :
#Component(modules={ModuleA.class})
#Singleton
public interface ComponentA {
void inject(MainActivity activity);
}
#Component(modules={ModuleB.class})
#Singleton
public interface ComponentB {
void inject(MainActivity activity);
}
#Module
public class ModuleA {
Application mApplication;
public ModuleA(Application application) {
mApplication = application;
}
#Provides
public Application provideApplication() {
return mApplication;
}
}
#Module
public class ModuleB {
Fragment mFragment;
public ModuleB(Fragment fragment) {
mFragment = fragment;
}
#Provides
public Fragment provideFragment() {
return mFragment;
}
}

If you try to inject an Activity with a Component Dagger 2 will check that Component can provide every dependency annotated with #Inject
Right now your Components (probably) only provides one of two dependencies. Make one component depend on the other one or make one of them take both Modules
Another possible solution could be change your Components to this:
#Singleton
public interface ComponentA {
Application getApplication();
}
#Component(modules={ModuleB.class})
#Singleton
public interface ComponentB {
Fragment getFragment();
}
and change your injecting code to this:
ComponentA componentA = ...
ComponentB componentB = ...
applicationA = componentA.getApplication();
fragmentB = componentB.getFragment();

Related

Dagger2.11 cannot be provided without an #Provides-annotated method

I am trying to understand dagger.android framework that is included in Dagger 2.11. I wrote a sample code that implements some scopes, #Singleton, #ActivityScope and #FragmentScope.
My Activity has a fragment, and fragment has a Toy object. I want that MainFragment belong to Activity Scope and Toy object belong to Fragment scope.
But I have an error, Could you help me please? What is the problem? :
Error:(22, 8) error: [dagger.android.AndroidInjector.inject(T)]
com.example.user.daggerapplication4.Models.Toy cannot be provided
without an #Provides-annotated method.
com.example.user.daggerapplication4.Models.Toy is injected at
com.example.user.daggerapplication4.ui.MainFragment.toy
com.example.user.daggerapplication4.ui.MainFragment is injected at
com.example.user.daggerapplication4.ui.MainActivity.injectedFragment
com.example.user.daggerapplication4.ui.MainActivity is injected at
dagger.android.AndroidInjector.inject(arg0) A binding with matching
key exists in component:
com.example.user.daggerapplication4.ui.MainActivityModule_BindMainFragment.MainFragmentSubcomponent
AppComponent and Module :
#Singleton
#Component(modules = {
AndroidInjectionModule.class,
AppModule.class,
ActivityBuilder.class
})
public interface AppComponent extends AndroidInjector<DaggerSample4Application> {
#Component.Builder
abstract class Builder extends AndroidInjector.Builder<DaggerSample4Application> {}
}
#Module
public class AppModule {
}
#Module
public abstract class ActivityBuilder {
#ActivityScoped
#ContributesAndroidInjector(modules = MainActivityModule.class)
abstract MainActivity bindMainActivity();
}
Scopes :
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface FragmentScoped {}
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface ActivityScoped {}
ActivityModule and FragmentModule
#Module()
public abstract class MainActivityModule {
#FragmentScoped
#ContributesAndroidInjector(modules = MainFragmentModule.class)
abstract MainFragment bindMainFragment();
}
#Module
public class MainFragmentModule {
#Provides
#FragmentScoped
Toy provideToy()
{
return new Puzzle();
}
}
Model Classes:
public interface Toy {
public String play();
}
public class Puzzle implements Toy {
#Override
public String play() {
Log.v("DaggerSample","Play with Puzzle");
return "Play with Puzzle";
}
}
MainActivity and MainFragment
public class MainActivity extends DaggerAppCompatActivity {
#Inject
MainFragment injectedFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainFragment mainFragment = (MainFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
// injectedFragment = new MainFragment();
if (mainFragment == null) {
mainFragment = injectedFragment;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.contentFrame, mainFragment);
transaction.commit();
}
}
}
public class MainFragment extends DaggerFragment {
private Button btnBuy;
private TextView textResult;
#Inject
Toy toy;
#Inject
public MainFragment()
{
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_main, container, false);
btnBuy = root.findViewById(R.id.btnBuy);
textResult = root.findViewById(R.id.textRresult);
btnBuy.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
showMessage(toy.play());
}
});
return root;
}
public void showMessage(String message) {
textResult.setText(message);
}
}
If you want to investigate the code, you can access with this link
I think while using interface it is better to use #Binds.
Try the method below.
#Module
public class MainFragmentModule {
#FragmentScoped
#Binds
public abstract Toy bindToy(Puzzle puzzle);
}
public class Puzzle implements Toy {
#Inject
public Puzzle(){
}
#Override
public String play() {
Log.v("DaggerSample","Play with Puzzle");
return "Play with Puzzle";
}
}
You have a few errors to fix in your code to get your project to compile. But first, rule of thumb for efficient Dagger - always prefer making your modules as abstract classes with abstract #Binds methods or if not possible with static #Provides methods. This means you need to make AppModule an abstract class, otherwise your project won't compile as per the code you posted here.
The main reason why your code doesn't compile is because Puzzle doesn't have a constructor that is annotated with #Inject:
public class Puzzle implements Toy {
#Inject // Add this
public Puzzle() {
}
#Override
public String play() {
Log.v("DaggerSample","Play with Puzzle");
return "Play with Puzzle";
}
}
Next, you need to make the following changes to this module:
#Module
public class MainFragmentModule { // Make this class abstract
#Provides // Change this to #Binds instead
#FragmentScoped
Toy provideToy() // Change this method to look like this below method
{
return new Puzzle();
}
#Binds
#FragmentScoped
abstract Toy bindPuzzle(Puzzle puzzle);
}
If you have other classes that implement Toy interface that you want to inject, you'll have to use qualifiers (#Named annotation) to tell Dagger which implementation to inject.
You cannot inject a fragment to the activity that is hosting it. Instead, you must create the fragment and add it using the fragment manager instead.
public class MainActivity extends DaggerAppCompatActivity {
#Inject // Remove this
MainFragment injectedFragment; // And this if you don't use this field
You can't annotate the fragment constructor with #Inject. Fragment is an Android Component and Android Components cannot be injected via constructor injection. The only way you can inject Android Components is via member injection, which is already done for you if your fragment inherits from DaggerFragment. Notice that if you're using support library Fragments, make sure to use DaggerFragment variant which is from the support package.
You haven't included your DaggerSample4Application code so I can't tell if you're doing something wrong there, but the main point is that this class needs to extend DaggerApplication and implement some methods. I have a complete working sample that you can check out:
https://github.com/Nimrodda/dagger-androidinjector
It's the source code for an article I wrote about Dagger Android injection https://android.jlelse.eu/android-and-dagger-2-10-androidinjector-5e9c523679a3
I highly recommend you check it out to get better understanding.

How to inject into a java class that doesn't have any activity or fragment using dagger2

Android Studio 2.2.2
I have a NewsListModelImp class which is the model in the MVP.
I want to inject my retrofit service into the model. However, as NewsListModelImp doesn't contain any reference to a context or activity I cannot call getApplication(). Which is what you would do if you were in a activity or fragment. I don't want to pass any context or activity in the constructor of NewsListModeImp as that would have to come from the presenter and I want to avoid any android stuff there.
public class NewsListModelImp implements NewsListModelContract {
#Inject
NYTimesSearchService mNYTimesSearchService;
public NewsListModelImp() {
((NYTimesSearchApplication)getApplication()).getAppComponent().inject(this);
}
}
My Application class
public class NYTimesSearchApplication extends Application {
private AppComponent mAppComponent;
public void onCreate() {
super.onCreate();
/* Setup dependency injection */
createAppComponent();
}
private void createAppComponent() {
mAppComponent = DaggerAppComponent
.builder()
.retrofitModule(new RetrofitModule())
.build();
}
public AppComponent getAppComponent() {
return mAppComponent;
}
}
My provides module
#Module
public class RetrofitModule {
private Retrofit retrofit() {
return new Retrofit
.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
#Provides
#Singleton
public NYTimesSearchService providesNYTimesSearch() {
return retrofit().create(NYTimesSearchService.class);
}
}
My Appcomponent
#Singleton
#Component(modules = {RetrofitModule.class})
public interface AppComponent {
void inject(NewsListModelImp target);
}
Many thanks for any suggestions,
Dagger-2 works reccurently. So if inside Activity (or Fragment) object is injected and it's constructor is properly annotated with #Inject annotation, the constructor's parameters will be injected too.
Suppose inside the application you would like to inject:
#Inject NyTimesPresenter presenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((NYTimesSearchApplication) getApplication()).getAppComponent().inject(this);
}
The constructor of NyTimesPresenter must be annotated with #Inject:
public class NyTimesPresenter {
NewsListModelImp newsListModel;
#Inject
public NyTimesPresenter(NewsListModelImp newsListModel) {
this.newsListModel = newsListModel;
}
}
NewsListModelImp constructor must also be annotated with #Inject:
public class NewsListModelImp implements NewsListModelContract {
NYTimesSearchService mNYTimesSearchService;
#Inject
public NewsListModelImp(NYTimesSearchService nYTimesSearchService) {
this.mNYTimesSearchService = nYTimesSearchService;
}
}
Then everything will be injected properly.
Why the parameters should be passed to class as constructors' parameters? Such design pattern conforms SOLID principles. Object dependencies are injected into objects and not created inside it and such code is easily testable (in tests dependencies can be replaced ie. by Mock's)
EXTRA INFO:
It is possible to inject objects implementing specific interfaces. Such technique is described here. In your case NyTimesPresenter can have NewsListModelContract as it's dependency instead of NewsListModelImp. To do this add another module to your AppComponent:
#Singleton
#Component(
modules = {
RetrofitModule.class,
AppModule.class
})
public interface AppComponent {
AppComponent method to provide concrete class implementing interface should look like:
#Singleton
#Module
public abstract class AppModule {
#Binds
public abstract NewsListModelContract provideNewsListModelContract(NewsListModelImp newsListModelImp);
}
The implementation of NyTimesPresenter should change (just to replace concrete class with interface it implements):
public class NyTimesPresenter {
NewsListModelContract newsListModel;
#Inject
public NyTimesPresenter(NewsListModelContract newsListModel) {
this.newsListModel = newsListModel;
}
}

Android Test with Dagger mock inject constructor

Hi i've got a following Problem. I want to write android tests with espresso for the Ui and in order to have tests that are not flaky i want to mock my presenter.
I use Dagger in the App. My Configuration is as Following:
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
//some injections
//some providings
}
I have a Module for the Component
#Module
public class AppModule {
//providings for component
}
then i have also a component for the activities with a module for the component
#PerActivity
#Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
//inject activites
//provide subcomponents for activites
}
then i have subcomponents for my pages
#PerActivity
#Subcomponent(modules = InfoModule.class)
public interface InfoComponent {
void inject(DetailActivity activity);
}
and a module for the subcomponent
#Module
public class InfoModule {
#Provides
public DetailPresenter provideDetailPresenter(ShowDetailsUseCase showDetailsUseCase,
OtherUseCase getPoisUseCase,
AccountManager accountManager, Navigator
navigator) {
return new DetailPresenter(showDetailsUseCase, otherUseCase, accountManager, navigator);
}
}
and then the detail Activity Injects the DetailPresenter
public class DetailActivity extends BaseActivity {
#Inject
DetailPresenter mPresenter;
InfoComponent mComponent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mComponent = getActivityComponent().provideInfoModule(new InfoModule());
mComponent.inject(this);
mPresenter.bindView(this);
mPresenter.onCreate(new PresenterBundle(getIntent().getExtras(), savedInstanceState));
}
//functionality of detailActiviy
}
then i have the presenter which uses constructor injection
public class DetailPresenter extends BasePresenter {
private ShowDetailsUseCase mDetailsUseCase;
private final OtherUseCase getPoisUseCase;
private AccountManager accountManager;
private Navigator navigator;
#Inject
public DetailPresenter(ShowDetailsUseCase getDetailsUseCase, OtherUseCase getPoisUseCase,
AccountManager
accountManager, Navigator navigator) {
this.mDetailsUseCase = getDetailsUseCase;
this.getPoisUseCase = gotherUseCase;
this.accountManager = accountManager;
this.navigator = navigator;
}
#Override
public void onCreate(#Nullable PresenterBundle bundle) {
super.onCreate(bundle);
//other things to do on initialization
((DetailView) getView()).showDetails(getDetailsFromUseCase());
}
}
Now in the test i want to do mock the presenter:
#RunWith(AndroidJUnit4.class)
public class DetailActivityTest {
#Rule
public final ActivityTestRule<DetailActivity> main = new ActivityTestRule<DetailActivity>(DetailActivity.class, true, false);
#Rule
public final DaggerMockRule<AppComponent> rule=new EspressoDaggerMockRule();
#Mock
DetailPresenter presenter; //does not work because #Inject constructor
#Test
public void locationTest() {
Details details = generateDetails();
launchActivity();
doAnswer(answer -> {
activity.showDetails(details);
return null;
}
).when(presenter).onCreate(any());
//espresso verify afterwards
}
}
but if i try to mock the following error shows:
java.lang.RuntimeException: Error while trying to override objects:
a.b.c.ui.mvp.presenter.DetailPresenter
You must define overridden objects using a #Provides annotated method instead of using #Inject annotation
does someone have an idea how I am able to mock the presenter even with #Inject constructor and dependencies.
I do not want to mock the data layer because then I have to mock database, apiClient, cacheData and so on. And some of the datalayer also have inject dependencies so i cannot mock them either.
Thank you in advance
The DetailPresenter class is created in the InfoModule, so you don't need the Inject annotation. The error you get is because using DaggerMock you can replace only the objects created in a module. In your example you are already creating it in a module, you just need to remove the Inject annotation.

Dagger 2 error: "RepositoryImpl cannot be provided without an #Inject constructor or from an #Provides-annotated method"

For example, I have following interface:
public interface Repository {
Observable<Pojo> getPojos();
}
And its implementation:
public class RepositoryImpl implements Repository {
public RepositoryImpl() {
}
#Override
public Observable<Pojo> getPojos() {
return null;
}
}
Module:
#Module
class AppModule {
public AppModule() {
}
#Provides
#Singleton
Repository provideRepositoryImpl() {
return new RepositoryImpl();
}
}
And component:
#Singleton
#Component(modules = { AppModule.class })
public interface AppComponent {
void inject(MainActivity mainActivity);
}
When I trying to build project, I receive error as in question title. What problem in my code?
Read your error carefully (emphasis mine):
Dagger 2 error: “RepositoryImpl cannot be provided without an #Inject constructor or from an #Provides-annotated method”
Generally this means you've tried to #Inject RepositoryImpl, not #Inject Repository. This is especially important because your Module directly calls the RepositoryImpl constructor rather than letting Dagger create your RepositoryImpl using an #Inject-annotated constructor. (If you had, you could make RepositoryImpl a parameter of your #Provides method or switch to a #Binds method, and you have your choice between injecting the interface versus the implementation.)
The way I setup Dagger 2 is in my projects Application I add the injection component. like so.
public class NyApplication extends Application {
InjectionComponent component;
#Override
public void onCreate() {
super.onCreate();
setDagger();
}
private void setDagger() {
component = DaggerAppComponent.builder()
.appComponent(new AppModule())
.build();
component.inject(this);
}
public InjectionComponent getComponent() {
return component;
}}
and then int my activity whatever it is. I inject on it's onCreate like this.
public class MainActivity extends Activity {
#Inject
Object object;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApplication) getApplication()).getComponent().inject(this);
}}
I hope this helps you.

Dagger v2: Inject 2 different scopes into one object

I have moduleA setup as an application wide singleton provider, ModuleB as a user related object provider
My user display fragment will use system wide bus to send message to others and use user related object to display.
Problem cannot inject different scrope class into one object. Use component.getX method works fine, but inject is prefered way.
Error message:
#UserScope may not reference bindings with difference scopes: #Provides #Singleton Bus ModuleA.provideBus()
#Module
public class ModuleA {
#Provides #Singleton Bus provideBus() {...}
}
Module B as user related Info provider
#Module
public class ModuleB{
private final User user;
public ModuleB(User user) {...}
#Provides #UserScope User provideUser() {}
#Provides #UserScope UserManager provideUserManager() {}
}
Components setup like following:
#Component (modules={ModuleA.class})
#Singleton
public interface ComponentA {
Bus getBus();
void inject(ClassA target);
}
#Component(modules={ModuleB.class})
#UserScope
public interface ComponentB {
User getUser();
UserManager getUserManager();
void inject(ClassA target);
}
class UserFragment exrtends Fragment {
#Inject Bus bus;
#Inject UserManager userManager;
public void onCreate() {
getComponentA().inject(this);
getComponentB().inject(this);
}
}
Try this configuration, it works for me. There is really a lack of good documentation about Dagger2 so I studied a few open-source examples of code that you can find in GitHub etc by keyword like Dagger2.
Application level Component
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
// exported for child-components
Bus eventBus();
}
Application level Module
#Module
public class AppModule {
#Provides #Singleton
Bus provideBus() {
return BusProvider.getInstance();
}
}
Activity level Component
#ActivityScope
#Component(dependencies=AppComponent.class, modules=MainActivityModule.class)
public interface MainActivityComponent {
void inject( MainActivity mainActivity );
}
Activity level Module
#Module
public class MainActivityModule {
private final MainActivity mActivity;
public MainActivityModule( MainActivity activity ) {
mActivity = activity;
}
#Provides
MainActivityTitleController provideTitleController() {
return new MainActivityTitleController( mActivity );
}
}
Android Application class
public class MyApplication extends Application {
private AppComponent mAppComponent;
#Override
public void onCreate() {
super.onCreate();
// Dagger2
mAppComponent = Dagger_AppComponent.builder()
.appModule( new AppModule( this ))
.build();
}
public AppComponent getComponent() {
return mAppComponent;
}
public static AppComponent getComponent( Context context ) {
return ((MyApplication)context.getApplicationContext()).getComponent();
}
}
And finally Activity
public class MainActivity extends ActionBarActivity {
// Injectable fields
#Inject Bus mEventBus;
#Inject MainActivityTitleController mTitleController;
private MainActivityComponent mComponent;
#Override
protected void onCreate( Bundle savedInstanceState ) {
// Dagger2
mComponent = Dagger_MainActivityComponent.builder()
.appComponent( ((MyApplication)getApplication()).getComponent() )
.mainActivityModule( new MainActivityModule( this ) )
.build();
mComponent.inject( this );
}
}
I think the main problem in your code snippets you provided, is that your ModuleB should have a dependency on ModuleA to correctly provide the singleton with the error you were getting. I.e. this should work:
#Component(modules={ModuleB.class}, dependencies = ComponentA.class)
#UserScope
public interface ComponentB {
User getUser();
UserManager getUserManager();
void inject(MainActivity target);
}
I recreated your classes and made some assumptions to fill in the blanks, and it seems to work fine. You can see the full working code here on GitHub. The only difference in my code is, what you called ClassA/UserFragment I just called MainActivity but otherwise the structure is the same.

Categories

Resources