mtrakal mtrakal - 1 month ago 16
Android Question

Dagger2 Inject a lot of activities/fragments/services (possible get a lot of NPE)

We used RoboGuice, but it's deprecated I start replace it with Dagger2.


// https://github.com/google/dagger
compile('com.google.dagger:dagger:2.7')
annotationProcessor 'com.google.dagger:dagger-compiler:2.7'
provided 'org.glassfish:javax.annotation:10.0-b28'





@Module
public class ApplicationModule {
Application mApp;

public ApplicationModule(@NonNull Application app) {
Preconditions.checkNotNull(app);
mApp = app;
}

@Provides
@Singleton
public SharedPreferences providesSharedPrefs() {
return PreferenceManager.getDefaultSharedPreferences(mApp);
}

@Provides
@Singleton
public DateHelper providesDateHelper() {
return new DateHelper(mApp);
}

@Provides
@Singleton
public PersistentConfig providesPersistentConfig() {
return new PersistentConfig(mApp);
}

@Provides
@Singleton
public OttoBus providesOttoBus() {
return new OttoBus();
}
}





public class Application extends MultiDexApplication {
private ApplicationComponent mApplicationComponent;

@Override
public void onCreate() {
super.onCreate();
mApplicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
mApplicationComponent.inject(this);
}

public static Application getApp(@NonNull Context context) {
return (Application) context.getApplicationContext();
}

public static ApplicationComponent getApplicationComponent(@NonNull Context context) {
return getApp(context).getApplicationComponent();
}
}





And after everywhere when I want to inject ApplicationComponent

For example
MainActivity


public class MainActivity extends AppCompatActivity {
@Inject
PersistentConfig mPersistentConfig;

@Inject
OttoBus mOttoBus;

@Override
public void onCreate(Bundle savedInstanceState) {
Helper.manageRotation(this);
super.onCreate(null);
setContentView(R.layout.main_layout);
Application.getApplicationComponent(this).inject(this);
}
}


Application.getApplicationComponent(context).inject(this);


First question: I'm really confused about
interface ApplicationComponent
which must provide all activities/fragments/services (etc) where I want to use injection. But I can't use generic objects like
Activity / Fragment
. Or am I really out of reality and don't understand how Dagger2 works?

Because this is really crazy for project with about 50+ activities and a tons of fragments/services...

@Singleton
@Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
void inject(@NonNull Application app);
void inject(@NonNull MainActivity object);
void inject(@NonNull DispatcherActivity object);
void inject(@NonNull DateTimeHelper object);
void inject(@NonNull DatabaseHelper object);
void inject(@NonNull LandingPageActivityFragment object);
void inject(@NonNull RedirectActivity object);
void inject(@NonNull CategoryFragment object);
void inject(@NonNull BaseModuleFragment object);
void inject(@NonNull NotificationHelper object);
void inject(@NonNull RecordAdapter object);
void inject(@NonNull PagingProvider object);
void inject(@NonNull FilterDialog object);
... next 100+ injections?
}


Said me, that it can't be real...

Second question: How I can provide to inject generic classes, when I can't use it like
void inject(@NonNull NotificationHelper<? extends GenericObject> object);
because it require specific object. So I must write all this objects inside
ApplicationComponent
and not use
?
notation?

It's a much more than just crazy :(. Maybe better stay with RoboGuice which is much more developer friendly and don't need make this overhead and manual check every injected objects? When I forgot add them to this list, I will get
NPE
in runtime (when I will not test it a lot it will crash customers).

It's much faster write it manually, than make a list of all object when it's not possible to use generic objects like
Activity / Fragment / Service
.

Is there a better solution, when I don't want use same generic
BaseActivity
which will inject every part of
ApplicationModule
and every activity will be extended by this huge
BaseActivity
?

Answer

This question has aspects of a complaint, but to attempt an answer:

I'm really confused about interface ApplicationComponent which must provide all activities/fragments/services (etc) where I want to use injection. But I can't use generic objects like Activity / Fragment. Or am I really out of reality and don't understand how Dagger2 works?

This is, indeed, how Dagger 2 works; it you must statically supply the type of the injection target inside the injector (component) and you cannot use 'generic' (covariant) types. Dagger 2 does this in order to maintain a DI framework that is 100% static.

Maybe better stay with RoboGuice which is much more developer friendly and don't need make this overhead and manual check every injected objects

Yes Roboguice is more friendly in the sense that you don't have to worry about specifying the injection targets. However, consider the following in Roboguice: 1. The 'red stacktrace of death' you get when you set up your object graph incorrectly 2. The fact that you cannot get see which implementations of interfaces are actually being used in your project with Find Usages which can also be 'developer unfriendly'

Is there a better solution, when I don't want use same generic BaseActivity which will inject every part of ApplicationModule and every activity will be extended by this huge BaseActivity?

Well, it would depend which dependencies you are using and where. If you have a small list of dependencies that you want to inject everywhere, that may be the best solution i.e., make a BaseActivity that receives injection of these and makes this available to all of your subclasses. Alternatively, you can use sub-components and modules you can divide up your object graph so that you can group consumers/injection targets together with the correct modules. Then you don't need to have one 'god' component that lists all of the injection sites.

Second question: How I can provide to inject generic classes, when I can't use it like void inject(@NonNull NotificationHelper object); because it require specific object. So I must write all this objects inside ApplicationComponent and not use ? notation?

Yes, you must supply the invariant type of the injection target. You don't need to write it all inside the ApplicationComponent, you may create subcomponents that correspond with the consumption patterns of the dependencies in your project.

(disclosure: as someone who is currently trying to migrate a project from Roboguice to Dagger 2 I am sympathetic to your complaint)

Comments