Dagger injects, complete confusion - android

I'm using dagger:1.2.2. There're two modules. BaseModule serves an EventBus for an Activity and ActivityModule serves NavigationController for the same Activity.
#Module(injects = { `does NOT have MainActivity.class registered`},
library = true
)
public class BaseModule {
#Provides #Singleton public EventBus provideEventBus() {
return DefaultEventBus.create();
}
#Provides #Singleton public Application provideApplication() {
return mApp;
}
#Provides #IsTablet public boolean isTablet(Application app) {
return app.getResources().getBoolean(R.bool.is_tablet);
}
}
#Module(
addsTo = BaseModule.class,
injects = MainActivity.class
)
public class ActivityModule {
#Provides
NavigationController provideNavigationController(Application app,#IsTablet boolean isTablet) {
return new NavigationController(app, isTablet);
}
}
Activity's fields:
#Inject
EventBus mEventBus;
#Inject
NavigationController mNavigationController;
NavigationController's ugly ctor:
public NavigationController(Context context, FragmentManager fm, List<Behaviour> behaviourList, #Nullable Class lastRoot,
#IsTablet boolean isTablet, Application application) {
This code passes static validation and performs well with
protected void onCreate(Bundle savedInstanceState) {
ObjectGraph activityGraph = getBaseModuleGraph(this).plus(new ActivityModule(this));
activityGraph.inject(this);
}
It crushes as expected due to unsatisfied dependency provided by ActivityModule if i just apply:
getBaseModuleGraph(this).inject(this);
The thing that bugs me is that BaseModule's inject doesn't contain MainActivity while it provides EventBus dependency for it. If I add this inject it gonna complain about unresolved NavigationController that is provided by ActivityModule. Setting complete=false on the BaseModule results in runtime exception
NavigationController has no injectable members. Do you want to add an
injectable constructor? required by class
MainActivity
I don't quite get it.
Ultimately I wanted BaseModule's injects to be more explicit by including MainActivity because it provides EventBus as I mentioned.
Could you help me out understanding it?

Related

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 multiple Injection on 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();

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.

dagger cannot inject type parameter field

I'm working on an android application and I'm trying to inject a field which is type parameterized in an abstract class : BaseListFragment
public abstract class BaseListFragment<E, A extends ArrayAdapter<E>, S> extends BaseFragment
{
#Inject protected S service;
}
But I get this following error at compile :
error: cannot find symbol class S
Here is my code for BaseFragment :
public class BaseFragment extends Fragment
{
#Override public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
((App) getActivity().getApplication()).inject(this);
}
}
here is my service module :
#Module(
complete = false,
library = true
)
public class ServiceModule
{
#Provides #Singleton BucketService provideBucketService(RestAdapter restAdapter)
{
return restAdapter.create(BucketService.class);
}
#Provides #Singleton ProjectService provideProjectService(RestAdapter restAdapter)
{
return restAdapter.create(ProjectService.class);
}
#Provides #Singleton ShotService provideShotService(RestAdapter restAdapter)
{
return restAdapter.create(ShotService.class);
}
#Provides #Singleton TeamService provideTeamService(RestAdapter restAdapter)
{
return restAdapter.create(TeamService.class);
}
#Provides #Singleton UserService provideUserService(RestAdapter restAdapter)
{
return restAdapter.create(UserService.class);
}
}
And here is an example of a class extending BaseListFragment :
public class ProjectFragment extends BaseEntityFragment<Project, ProjectViewAdapter, ProjectService>
{
}
Is there anyway to inject a parameterized type ?
Regards,
As of the 2.0.1 release, Dagger 2 does support the type of injection that you're talking about. Take a look at GenericTest for all of the various permutations.
I had the same problem and got around it by adding a non-parameterized inner class and injecting into that. Then using a getter to get the injected class out.
It looks like this:
public class MainClass<T>{
UtilityClass utility;
public MainClass(){
utility = new InjectorHelper().getHelper();
}
...
public static class InjectorHelper{
#Inject
UtilityClass utilityClass;
public InjectorHelper(){
Injector.getInstance().getAppComponent().inject(this);
}
public UtilityClass getUtilityClass(){
return utilityClass
}
}
}
I also could not get it work either and I think it's against the design philosophy of dagger, which is to generate code that is exactly what a developer would also write.
In this case, it generates an injection adapter for the abstract class and another one for concrete class. With generic arguments to the abstract class, you essentially have to inject the fields from the abstract class in each concrete class injector.
If it wasn't an android object (which the android runtime creates), I'd suggest to pass the service in the call to the super constructor.
In this case, I'd suggest to inject the service in the concrete classes and to provide it using an abstract method:
protected abstract S getService();

Dagger 1.x #Inject throws an IllegalArgumentException

I want to replace our component registry (with dexfile class loader magic) with an dependency injection framework for Android.
The first try is dagger.
When trying I get the following error:
11-06 13:05:41.040 16269-16269/com.daggertoolkitexample E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity ComponentInfo{daggertoolkitexample/com.dagger.MyActivity}: java.lang.IllegalArgumentException: No inject registered for members/com.dagger.MyActivity. You must explicitly add it to the 'injects' option in one of your modules.
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2295)
...
Caused by: java.lang.IllegalArgumentException: No inject registered for members/com.dagger.MyActivity. You must explicitly add it to the 'injects' option in one of your modules.
at dagger.ObjectGraph$DaggerObjectGraph.getInjectableTypeBinding(ObjectGraph.java:302)
at dagger.ObjectGraph$DaggerObjectGraph.inject(ObjectGraph.java:279)
at com.dagger.MyApplication.inject(MyApplication.java:39)
at com.dagger.MyBaseActivity.onCreate(MyBaseActivity.java:18)
at com.dagger.MyActivity.onCreate(MyActivity.java:22)
at android.app.Activity.performCreate(Activity.java:5372)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1104)
...
I can fix it if i inject my activity in the #Module. Its work without exception.
#Module(
library = true,
injects = MyActivity.class)
public class AuthManagementModul {...}`
But this is not that i want.
I don´t can and want to know all users of my component.
Has everyone an idea what's wrong?
Here is my example code:
public class MyBaseActivity extends ActionBarActivity {
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApplication) getApplication()).inject(this);
}
}
...
public class MyActivity extends MyBaseActivity {
#Inject AuthManagement authManagement;
...
}
...
public class MyApplication extends Application{
private ObjectGraph graph;
#Override
public void onCreate() {
super.onCreate();
graph = ObjectGraph.create(new AuthManagementModul(this));
}
public void inject(Object object) {
graph.inject(object);
}
}
...
#Module(
library = true
)
public class AuthManagementModul {
private final Application application;
public AuthManagementModul(Application application) {
this.application = application;
}
#Provides
#Singleton
AuthManagement provideAuthManagement() {
return new AuthManagementImpl(application);
}
}
In this case, you don't want to add injects= to your AuthManagementModul, but rather to an activity-specific module which includes it.
Dagger 1.x uses injects= as a signal for what graph-roots to analyze, so they must be present -but they need not be present on leaf-node library modules - just on a module the activity uses. Consider breaking up your modules on more partitioned lines like so:
#Module(
injects = {
... all your activities
},
includes = {
AuthManagementModul.class,
ApplicationModule.class
}
)
class EntryPointsModule {}
#Module(library = true, complete = false)
class AuthManagementModul {
#Provides
#Singleton
AuthManagement provideAuthManagement(Application application) {
return new AuthManagementImpl(application);
}
}
#Module(library = true)
class ApplicationModule {
private final Application application;
public ApplicationModule(Application application) {
this.application = application;
}
#Provides
#Singleton
Application application() {
return application;
}
}
Then create your graph like so:
public class MyApplication extends Application{
private ObjectGraph graph;
#Override
public void onCreate() {
super.onCreate();
// AuthManagementModul is automatically included because it has a default
// constructor and is included by EntryPointsModule
graph = ObjectGraph.create(new EntryPointsModule(), new ApplicationModule(this));
}
public void inject(Object object) {
graph.inject(object);
}
}
There are other ways to structure this - you could just have ApplicationModule include the AuthModule and declare injects, so you only have two modules, etc. I suggested this way because ApplicationModule is then a separate concern whose only role is to hoist the Application instance into the graph, AuthManagementModul is exclusively there to support the auth function, and EntryPointsModule is there to be the front of the whole graph.
If you migrate to Dagger2, this structure is also convenient in that EntryPointsModule naturally converts to a #Component.

Categories

Resources