Dagger v2: Inject 2 different scopes into one object - android

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.

Related

How to #Provide services with Dagger 2

I'm using Dagger 2 for DI in my MVP application. The application has different services which are responsible for making requests like getting product data or doing an order.
Most of the time each screen has his own #Component interface and #Module class and in these modules the corresponding presenters are provided.
In some cases a service is only used by one presenter. In this case I just #Provide it in that module. But I have a lot of services which are used multiple times in different presenters. At the moment I have put them all in the ApplicationModule, expose them in the ApplicationComponent and add a dependency to ApplicationComponent in the 'screen components' if their presenters need one of the services. Now the problem is, that almost every component is depending on ApplicationComponent, which I think is wrong.
How can I handle this the best way? Should I create a module for each service and add it to the components that need it? I also want to keep scoping in mind and think the services should be added to the #Singleton scope.
ApplicationComponent
#Singleton
#Component(modules = {ApplicationModule.class, ContextModule.class}
public interface ApplicationComponent {
void inject(MyApplication application);
Context getContext();
ProductDataService getProductDataService();
AnalyticsService getAnalyticsService();
CartService getCartService();
...
}
ApplicationModule
#Module
public class ApplicationModule {
#Provides
static ProductDataService provideProductDataService(ProductDataServiceImpl productDataService) {
return productDataService;
}
#Provides
static AnalyticsService provideAnalyticsService(AnalyticsServiceImpl analyticsService) {
return analyticsService;
}
#Provides
static CartService provideCartService(CartServiceImpl cartService) {
return cartService;
}
...
}
ProductDataServiceImpl
#Singleton
public class ProductDataServiceImpl implements ProductDataService {
private WebService webService;
#Inject
public ProductDataServiceImpl(WebService webService) {
this.webService = webService;
}
...
}
DependentComponent
#Component(modules = DependentModule.class, dependencies = ApplicationComponent.class)
public interface DependentComponent {
void inject(MyFragment fragment)
}
DependentModule
#Module
public class DependentModule {
private MyView myView;
public DependentModule(MyView myView) {
this.myView = myView;
}
#Provides
public MyPresenter provideMyPresenter(ProductDataService productDataService, AnalyticsService analyticsService) {
return new MyPresenterImpl(myView, productDataService, analyticsService)
}
}

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.

Why is Dagger2 asking me to provide something that the component is not supposed to provide?

I'm playing around trying to learn Dagger2. Just when I thought I was getting it, I seem to have gotten stuck. My application has two components, ApplicationComponent (singleton) and StripeComponent (1:1 activity) , which inherit an empty interface for the sake of readability.
Then it has two modules, ApplicationModule and StripeModule.
#Singleton #Component(modules = ApplicationModule.class)
public interface ApplicationComponent extends AbstractComponent ...
#PerActivity #Component(modules = { StripeModule.class }) public interface StripeComponent
extends AbstractComponent ...
#Module public class ApplicationModule
#Module public class StripeModule
One of the objects ApplicationModule provides is a Navigator, and I'm fairly sure than the way it does it is fairly correct:
#Provides #Singleton Navigator provideNavigator() {
return new Navigator();
}
This is a very simple class with pretty much nothing in it yet:
#Singleton public class Navigator
Then when I generate the code, an extra provision factory is generated from StripeModule - StripeModule_ProvideNavigatorFactory. And then the compiler whines that I'm not providing it - which is true, and intentional. It should be provided by the application component only. The question is, why is this factory being generated then? Why doesn't Dagger2 understand that StripeModule is not supposed to provide a navigator?
Assuming you have a StripeActivity class that uses StripeComponent to inject itself, then you might end up with a scenario like this one
public class StripeActivity extends AppCompatActivity {
#Inject
Navigator navigator;
#Override
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
CustomApplication application = (CustomApplication)getApplicationContext();
StripeComponent stripeComponent = createComponent(application);
stripeComponent.inject(this);
}
protected StripeComponent createComponent(CustomApplication application) {
return DaggerStripeComponent.builder()
.applicationComponent(application.getApplicationComponent())
.build();
}
}
public class CustomApplication extends Application {
ApplicationComponent applicationComponent;
#Override
protected void onCreate() {
super.onCreate();
applicationComponent = createApplicationComponent();
}
protected ApplicationComponent createApplicationComponent() {
return DaggerApplicationComponent.create();
}
public ApplicationComponent getApplicationComponent() {
return applicationComponent;
}
}
#Component(modules={ApplicationModule.class})
#Singleton
public interface ApplicationComponent {
Navigator navigator();
}
#Component(dependencies={ApplicationComponent.class}, modules={StripeModule.class})
#PerActivity
public interface StripeComponent extends ApplicationComponent {
void inject(StripeActivity stripeActivity);
}
#Module
public class ApplicationModule {
#Provides
#Singleton
Navigator navigator() {
return new Navigator();
}
}
#Module
public class StripeModule {
//#Provides
//#PerActivity
//...
}
#Scope
#Retention(RUNTIME)
public #interface PerActivity {
}
EDIT: For base class injection, you need to inject both the superclass and the subclass manually, and you need to specify both the superclass and the subclass in your component. In this case, it would work like this.
public abstract class BaseActivity extends AppCompatActivity {
#Inject
Navigator navigator;
#Override
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
CustomApplication application = (CustomApplication)getApplicationContext();
ApplicationComponent component = createComponentAndInjectSelf(application);
component.inject(this);
}
protected abstract ApplicationComponent createComponentAndInjectSelf(CustomApplication application);
}
public class StripeActivity extends BaseActivity {
#Override
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
}
#Override
public StripeComponent createComponentAndInjectSelf(CustomApplication application) {
StripeComponent stripeComponent = DaggerStripeComponent.builder()
.applicationComponent(application.getApplicationComponent())
.build();
stripeComponent.inject(this);
return stripeComponent;
}
}
#Component(modules={ApplicationModule.class})
#Singleton
public interface ApplicationComponent {
Navigator navigator();
void inject(BaseActivity baseActivity);
}
#Component(dependencies={ApplicationComponent.class}, modules={StripeModule.class})
#PerActivity
public interface StripeComponent extends ApplicationComponent {
void inject(StripeActivity stripeActivity);
}
You haven't included the exact error, or the code that actually depends on the Navigator.
But I'll assume the class that depends on Navigator is provided from within StripeModule or another module installed into StripeComponent.
StripeComponent and ApplicationComponent have to be related in some way in order for bindings in StripeComponent to use bindings provided by ApplicationComponent.
You can relate them either using component dependencies or subcomponents.
If you use subcomponents, you'd make StripeComponent a subcomponent of ApplicationComponent, which would mean that it can use any of the bindings in modules installed in ApplicationComponent, including the one for Navigator.
If you want to use component dependencies, you'd make StripeComponent depend on ApplicationComponent, and you'd add a method Navigator navigator() to ApplicationComponent. Bindings in StripeComponent can depend on any type returned by a method on a component it depends on.

Categories

Resources