Dagger 2 Android: inject the scoped activity - android

I'm using Dagger Android to link dependencies in my Android application.
I have 2 Activities, both have their own Scope (#ActivityScoped).
Here is my code so far :
MyApplication.java
public class MyApplication extends Application implements HasActivityInjector {
#Inject DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;
#Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder()
.application(this)
.build()
.inject(this);
}
#Override
public AndroidInjector<Activity> activityInjector() {
return activityDispatchingAndroidInjector;
}
}
AppComponent.java
#Singleton
#Component(modules = {
AndroidInjectionModule.class,
AppModule.class,
ActivityBindingModule.class
})
public interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
Builder application(Application app);
AppComponent build();
}
void inject(MyApplication app);
}
ActivityBindingsModule.java
#Module(subcomponents = ActivityComponent.class)
public abstract class ActivityBindingModule {
#ActivityScoped
#ContributesAndroidInjector
public abstract MainActivity contributeMainActivityInjector();
#ActivityScoped
#ContributesAndroidInjector
public abstract SecondActivity contributeSecondActivityInjector();
}
Let's say I have a class MyClass tied to ActivityScoped that I want to inject into both Activities :
MyClass.java
#ActivityScoped
public class MyClass {
#Inject
public MyClass(Activity activity) {
// Do something with the activity instance...
}
}
What I want to achieve is to inject into MyClass the Activity instance of the enclosing scope.
For example, if MyClass is injected into ActivityA, then the activity constructor argument of MyClass should be an instance of ActivityA.
What I tried :
#Binds annotated method to inject ActivityA and ActivityB as Activity. This doesn't work since Dagger does not allow binding multiple implementations to the same base type.
Map Multibindings doesn't suit my needs : I don't want MyClass to know about the Activity in which it is injected.
Maybe I'm missing something, as dependency injection is rather hard to setup. How can I do that with Dagger Android ?

#Binds annotated method to inject ActivityA and ActivityB as Activity. This doesn't work since Dagger does not allow binding multiple implementations to the same base type.
That's the right way to do so. You only get an error when you try to bind multiple implementations to the same type in the same component. So you should not bind both within your AppComponent, but to the #ActivityScoped subcomponents.
You have to create a module for each Activity, A and B, that binds the implementation to Activity. Then you just add that module to the Subcomponent that Dagger creates for you.
#ActivityScoped
#ContributesAndroidInjector(modules={MainActivityBindingsModule.class})
public abstract MainActivity contributeMainActivityInjector();
That way the binding only exists within the #ActivityScoped component and you should be able to repeat it.

Related

Exposing Dagger Components defined in an Android Library

How does an Android application in one gradle module include dagger #Modules defined in an other Android Library (.aar)?
I'm trying to have a generic library that provides a set of functionality defined thru dagger #Module's able to have have them injected into Applications that depend on that library
#Singleton
#Component(modules = {
AndroidSupportInjectionModule.class,
LibraryAppModule.class
})
public interface LibraryComponent extends AndroidInjector<DaggerApplication> {
#Component.Builder
interface Builder {
#BindsInstance
Builder application(Application application);
LibraryComponent build();
}
void inject(LibraryApplication app);
#Override
void inject(DaggerApplication instance);
}
#Module
public class LibraryAppModule {
#Provides
#Singleton
Context provideContext(Application application) {
return application;
}
#Singleton
#Provides
ServiceFoo provideServiceFoo(Context context) {
return new ServiceFoo(context);
}
#Singleton
#Provides
ServiceBar provideServiceBar(Context context, ServiceFoo serviceFoo) {
return new ServiceBar(context, serviceFoo);
}
}
Now in the Application that has a dependency for this library (build.gradle) I've tried the following:
implementation project(":Library")
Application using the library
#Singleton
#Component(modules = {
AndroidSupportInjectionModule.class,
LibraryAppModule.class
})
public interface ExampleApplicationComponent extends AndroidInjector<DaggerApplication> {
#Component.Builder
interface Builder {
#BindsInstance
Builder application(Application application);
ExampleApplicationComponent build();
}
void inject(ExampleApplication app);
#Override
void inject(DaggerApplication instance);
}
This is an example of the place where I'm getting the issue (a null object). This is in a separate module that depends on the library.
public class MainActivity extends AppCompatActivity {
#Inject
ServiceFoo serviceFoo;
#Inject
ServiceBar serviceBar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
serviceFoo.downloadImages();
serviceBar.uploadImages();
}
}
I expect to see the LibraryAppModule's services to be available in my Activities using #Inject. What am I doing wrong?
In order to resolve the issue 2 things needed to be done.
The MainActivity had to extend DaggerAppCompatActivity (this ensures during onCreate() for our Activity that inject() is called, but for this to work there needs to be an AndroidInjector for the Activity.
This is accomplished by using #ContributesAndroidInjector within a module that I defined and installed into the ExampleApplicationComponent
#Module
public abstract class ExampleApplicationActivityBuilder {
#ContributesAndroidInjector
abstract MainActivity bindMainActivity();
}
Now within the MainActivity, the Services I want injected from the library module are non-null.
Here's is a git commit that shows the difference implementing this fix.
https://github.com/PeregrineFalcon/DaggerExampleLibrary/commit/1ebfdecfa3684c7ac124b9f6a4cecd23712a74fd
Hope this helps someone else who trying to do a multi-module Dagger Android project with #Modules provided through the library.
A class that wants a property injected into it should call inject()!
Since you want serviceFoo injected in MainActivity, you should call inject() from onCreate(). However, where is inject() defined for you to just call it? Hence, you additionally declare inject() in ExampleApplicationComponent.
All that said, it looks like your understanding of dagger is incomplete. Recommend this blog post.

Dagger 2 Getting null object reference on create method

I can't understand what wrong I am doing here
Splash Activity :
public class SplashActivity extends BaseActivity implements SplashView {
#Inject
SplashPresenter presenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash_with_di);
presenter.getAppVersion();
}
}
Component :
#Component(modules = SplashModule.class)
public interface AppComponent {
SplashWithDiPresenter getSplashWithDiPresenter();
}
Splash Module :
#Module(includes = RetrofitModule.class)
public class SplashModule {
#Provides
SplashPresenter provideSplashPresenter(final SplashInteractorImpl interactor){
return new SplashPresenterImpl(interactor);
}
#Provides
SplashInteractor providesSplashInteractor(final ApiInterface apiInterface){
return new SplashWithDiInteractorImpl(apiInterface);
}
}
inside application class called this method in onCreate()
private void createComponent() {
appComponent = DaggerAppComponent.builder()
.splashModule(new SplashModule())
.build();
}
Getting null object reference on Splash activity on create method
-> presenter.getAppVersion();
You have injected your application dependencies and now you need to do the same with SplashActivity dependencies. So you need to create a new component for your activity, lets say SplashComponent, and add inject method to it like this:
#PerActivity
#Component(modules = SplashModule.class, dependencies = AppComponent.class)
public interface SplashComponent {
public void inject(SplashActivity activity);
}
And then in your SplashActivity in the onCreate method add injection like this:
public class SplashActivity extends BaseActivity implements SplashView {
#Inject
SplashPresenter presenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash_with_di);
DaggerSplashComponent.builder()
.appComponent(getAppication().getAppComponent())
.splashModule(new SplashModule())
.build()
.inject(this);
presenter.getAppVersion();
}
}
Note, that you need to call your presenter's method only after you have injected your dependencies with inject method!
Ideally you should look at using subcomponents for these use cases for injecting Android Components(Activities, Fragments, Services...). It's pretty simple and avoids coupling Injector Components with Android components enabling testing of these classes and switching of components for Espresso tests.
Add dagger-android deps to your project (com.google.dagger:dagger-android-support:2.x, com.google.dagger:dagger-android-processor:2.x)
And then make an abstract DepsModule module, for example -
#Module
public abstract class DepsModule {
#PerActivity
#ContributesAndroidInjector(modules={SplashModule.class})
SplashActivity provideSplashInjector()
}
And change you App Component to include AndroidSupportInjectionModule.class & DepsModule.class and you should remove SplashModule.classs from the list to avoid rebinding errors.
Now its as easy as having a DispatchingAndroidInjector<Activity> instance injected to you App class and implementing HasActivityInjector interface to return the dispatching injector.
Now in SplashActivity before invoking super.onCreate() call AndroidSupportInjection.inject(this)
#ContribbutesAndroidInjector automatically tells dagger to create a sub-component for SplashActivity and bind the injector factory with DispatchingAndroidInjector<Activity> removing the need for boilerplate sub-component classes
And other sub-component installations for different activities can be added to DepsModule to enable injecting them in a similar way.
This also helps in scope segregation as exposing a splash activity's bindings to the entire app component is not really good
Hope I could help. You can visit the dagger 2 android docs for more info on using Dagger 2

Dagger with Conductor Controllers

I am experimenting with the 'new' Android support in Dagger 2.
I want to set up the following architecture in Dagger:
Application => Activity => Controller (Conductor)
(Controller is basically a View that gets instantiated by the system. You can think of it like Fragments but without Dagger Injection support)
For each level I have defined a dependency: ApplicationDep, ActivityDep and ControllerDep.
My Controller should be able to inject all of these dependencies.
My Activity should be able to inject the ApplicationDep and the ActivityDep
My Application should only be able to inject the ApplicationDep
Everything works except in my Controller.
I am unable to inject the ActivityDep.
public class MyController extends Controller {
#Inject
ApplicationDependency applicationDependency;
//#Inject
//ActivityDependency activityDependency; // can not be provided
#Inject
ControllerDependency controllerDependency;
#NonNull #Override protected View onCreateView(#NonNull LayoutInflater layoutInflater, #NonNull ViewGroup viewGroup) {
ConductorInjection.inject(this);
return layoutInflater.inflate(R.layout.controller_main, viewGroup, false);
}
}
Currently I have my ControllerBindingModule bound on my ApplicationComponent, however this should be bound on the Activity in order for it to be also available in the Controller.
How can I do this?
#Singleton
#Component(modules = {
ApplicationModule.class,
ActivityBindingModule.class,
AndroidSupportInjectionModule.class,
ConductorInjectionModule.class,
ControllerBindingModule.class
})
interface AppComponent extends AndroidInjector<App> {
#Component.Builder
abstract class Builder extends AndroidInjector.Builder<App> {}
}
The full code can be found on Github.
Thanks.
It sounds like controller is a subcomponent of activity component.
I took a look at your GitHub, so I change some of your code to answer.
First, for the Activity injection.
Controller is not subcomponent of Appcomponent, so it only need ActivityBindingModule.
AppComponent.java
#Singleton
#Component(modules = {
AppModule.class
ActivityBindingModule.class,
AndroidSupportInjectionModule.class,
})
interface AppComponent {
#Component.Builder
abstract class Builder extends AndroidInjector.Builder<App> {}
}
For the same reason, we only need to implement HasActivityInjector in App.
App.java
public class App extends Application implements HasActivityInjector {
#Inject protected DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
#Override
public AndroidInjector<Activity> activityInjector() {
return dispatchingActivityInjector;
}
#Override
public void onCreate() {
super.onCreate();
DaggerAppComponent
.builder()
.create(this)
.inject(this);
}
}
Because I need to declare Controller as subcomponent of ActivityComponent,
I use step 2 & step 3 in Dagger documentation about injecting activity objects
Change your ActivityBindingModule
ActivityBindingModule.java
#Module(subcomponents = {MainActivityComponent.class})
public abstract class ActivityBindingModule {
#Binds
#IntoMap
#ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity>
bindYourActivityInjectorFactory(MainActivityComponent.Builder builder);
}
Create an ActivityComponent.
MainActivityComponent.java
#Subcomponent(modules = {MainActivityModule.class})
#ActivityScope
public interface MainActivityComponent extends AndroidInjector<MainActivity>{
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
Next, it time to set controller as a subcomponent of activitycomponent.
Add ControllerBindingModule.class to MainActivityComponent.
#Subcomponent(modules = {MainActivityModule.class, ControllerBindingModule.class})
#ActivityScope
public interface MainActivityComponent extends AndroidInjector<MainActivity>{
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
And implement HasControllerInjector in MainActivity.
MainActivity.java
public class MainActivity extends AppCompatActivity implements HasControllerInjector {
#Inject protected DispatchingAndroidInjector<Controller> dispatchingControllerInjector;
#Override
public DispatchingAndroidInjector<Controller> controllerInjector() {
return dispatchingControllerInjector;
}
No need to change other files, and what you want is done.

dagger-android custom scopes

I'm confused about scoped dependencies in Dagger using dagger-android.
Using #ContributesAndroidInjetor I have a code something like the following:
#Module
public abstract class ActivityBindingModule {
#ContributesAndroidInjector(modules = PotatoesModule.class)
public abstract MainActivity contributeMainActivityInjector();
#ContributesAndroidInjector
public abstract UserActivity contributeUserActivity();
}
The ActivityBindingModule is defined as a module in my AppComponent. But the problem is. How can I do something like
#UserScope
#Component(dependencies = AppComponent.class)
public interface UserComponent {...}
And annotate an Activity to use that scope? Is all my dependencies inside activity "local singletons"? Because each Activity injector is a subcomponent of AppComponent.
Maybe I'm not understanding the concept of "scopes" using dagger-android, I would be glad if someone could explain it.
Here's some clarification on scopes:
Say you had an AppComponent and you Annotate it with the #Singleton annotation:
#Singleton
#Component(modules = {
AndroidInjectionModule.class,
AppModule.class
})
public interface AppComponent extends AndroidInjector<BaseApplication> {
#Component.Builder
interface Builder{
#BindsInstance
Builder application(Application application);
AppComponent build();
}
}
And you had an AppModule which provide App level dependencies (i.e. a Retrofit Instance for example that you annotate with #Singleton):
#Module
public class AppModule {
#Singleton
#Provides
static Retrofit provideRetrofitInstance(){
return new Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
}
}
Then we can say that AppComponent owns the #Singleton scope and therefore the #Singleton annotation that you put on the Retrofit instance that you provided now has the same scope as the AppComponent - i.e. it's an application level scope.
If you want to scope to Activities - you should make a custom scope like this:
#Scope
#Documented
#Retention(RUNTIME)
public #interface UserScope {
}
Then in your ActivityBindingModule (that you've written), annotate the UserActivity with #UserScope if you want the UserActivity to "own" the #UserScope scope. Also, add a module next to the #ContributesAndroidInjector - let's call it UserModule.class:
#Module
public abstract class ActivityBindingModule {
#ContributesAndroidInjector(modules = PotatoesModule.class)
public abstract MainActivity contributeMainActivityInjector();
#UserScope
#ContributesAndroidInjector(modules = UserModule.class)
public abstract UserActivity contributeUserActivity();
}
And now, creating UserModule.class and annotating a provided dependency with #UserScope:
#Module
public class UserModule {
#UserScope
#Provides
static User provideUser(){
return new User();
}
}
This dependency now has the same scope as the UserActivity. So when UserActivity is destroyed and re-created, the dependency provided will also be destroyed and recreated.
To finish up:
Create a POJO User:
public class User {
public User() {
}
}
and now, if you go to your UserActivity and do:
public class UserActivity extends DaggerAppCompatActivity {
private static final String TAG = "UserActivity";
#Inject
User aUser;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user);
Log.d(TAG, "onCreate: " + aUser);
}
}
If you run your App now you will see a memory address being printed to the log. Rotate the device to destroy and re-create the activity and you'll see that the memory address changes. This is how we know that the #UserScope is working correctly.
If you want to see your Application scope in action i.e. #Singleton, then create an AppModule, add it to your AppComponent and provide a User dependency in that module and annotate it with #Singleton. Remember to use the #Named annotation too since you now have 2 dependencies that are provided with the same return type (that can both be accessed within the Activity Scope).
Go to your UserActivity again and Inject both Users (remember to use #Named). Log it in another logging statement and after rotating the device you will notice you have the same memory address for the Application scoped dependency.
I hope this cleared things up.
Is all my dependencies inside activity "local singletons"? Because each Activity injector is a subcomponent of AppComponent.
The subcomponents generated by dagger-android are unscoped, unless you annotate the #ContributesAndroidInjector-annotated method with a scope.
But the problem is. How can I do something like ... #Component(dependencies = AppComponent.class) ... And annotate an Activity to use that scope?
As far as I know, you can only use subcomponents with dagger-android. Furthermore activity subcomponents must be declared in modules installed in the application component, while fragment subcomponents may be declared in modules installed in either application, activity or fragment components.
I'm not sure what you mean by "annotate an Activity to use that scope" though.

Dagger 2.10/2.11 injecting Activity failing

I have been trying to, unsuccessfully, inject the Activity in a class ViewUtils. I have followed a couple of different posts but I can't seem to understand what am I missing in my implementation.
I know this is probably a repetition of the posts below and I really apologize for that but I honestly cannot see what am I missing. These are the posts I've found:
Dagger 2.10 Android subcomponents and builders
How to create custom scoped modules in dagger 2.10
https://google.github.io/dagger/subcomponents.html
My implementation is as follows:
AppComponent
#Component(modules = {
AppModule.class, AndroidSupportInjectionModule.class, ActivityBindingModule.class
}) #Singleton public interface AppComponent extends AndroidInjector<EmblyApp> {
#Component.Builder abstract class Builder extends AndroidInjector.Builder<EmblyApp> {}
}
ActivityBindingModule
#Module public abstract class ActivityBindingModule {
#ContributesAndroidInjector
abstract LoginActivity loginActivity();
}
LoginSubcomponent
#Subcomponent(modules = LoginSubcomponent.LoginActivityModule.class)
public interface LoginSubcomponent extends AndroidInjector<LoginActivity> {
#Subcomponent.Builder abstract class Builder extends AndroidInjector.Builder<LoginActivity> {}
#Module abstract class LoginActivityModule {
#Binds abstract Activity bindActivity(LoginActivity activity);
#Provides #ActivityScope static ViewUtils viewUtils(Activity activity) {
return new ViewUtils(activity);
}
}
}
ViewUtils
public class ViewUtils {
private final Activity activity;
#Inject public ViewUtils(Activity activity) {
this.activity = activity;
}
}
And the error i'm getting is:
Error:(14, 22) error: [dagger.android.AndroidInjector.inject(T)] android.app.Activity cannot be provided without an #Inject constructor or from an #Provides-annotated method.
android.app.Activity is injected at
com.emblyapp.app.ui.helpers.ViewUtils.<init>(activity)
com.emblyapp.app.ui.helpers.ViewUtils is injected at
com.emblyapp.app.ui.authentication.login.LoginActivity.viewUtils
com.emblyapp.app.ui.authentication.login.LoginActivity is injected at
dagger.android.AndroidInjector.inject(arg0)
What is wrong in here? Thanks for the help!
Edit: I forgot to mention my LoginActivity has the injection with the AndroidInjection
#Override protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
}
As specified in dagger android documentation:
Pro-tip: If your subcomponent and its builder have no other methods or supertypes than the ones mentioned in step #2, you can use #ContributesAndroidInjector to generate them for you. Instead of steps 2 and 3, add an abstract module method that returns your activity, annotate it with #ContributesAndroidInjector, and specify the modules you want to install into the subcomponent. If the subcomponent needs scopes, apply the scope annotations to the method as well.
Thus, we can get rid of LoginSubcomponent and perform following changes in ActivityBindingModule:
#Module
public abstract class ActivityBindingModule {
#ActivityScope
#ContributesAndroidInjector(modules = LoginActivityModule.class)
abstract LoginActivity loginActivity();
}
LoginActivityModule.java
#Module
abstract class LoginActivityModule {
#Binds
abstract Activity bindActivity(LoginActivity activity);
#Provides
#ActivityScope
static ViewUtils viewUtils(Activity activity) {
return new ViewUtils(activity);
}
}
Your custom application class:
public class MyApp extends DaggerApplication {
#Inject
DispatchingAndroidInjector dispatchingActivityInjector;
#Override
protected AndroidInjector applicationInjector() {
return DaggerAppComponent.builder().create(this);
}
}

Categories

Resources