In Dagger 2 Android, I was really liking the new simplified API, but I now require a little bit of customisation and it's making my head hurt.
I have a very common scenario: an app with some activities and every dependency is injected.
I'll describe my current implementation first and the problem afterwards. Bear with me for a second, please.
I started by creating the regular ActivitiesModule
#Module
abstract class ActivitiesModule {
#PerActivity
#ContributesAndroidInjector(modules = MainActivityModule.class)
abstract MainActivity bindMainActivity();
// More activities here, all with the scope #PerActivity
}
and then my MainActivityModule.java (and all the other activity modules) look like this
#Module
public abstract class MainActivityModule {
#Binds
#ActivityContext
#PerActivity
abstract Context provideActivityContext(MainActivity mainActivity);
#Binds
#PerActivity
abstract Activity provideActivity(MainActivity mainActivity);
#Binds
#PerActivity
abstract MainMvpView provideView(MainActivity mainActivity);
}
This is perfect, because every time I needed the activity or context in a class, I could just do this, without tightly coupling my object to a specific activity.
#PerActivity
public class MainPresenter {
private final MainMvpView view;
#Inject
MainPresenter(MainMvpView view) {
this.view = view;
}
}
This is great, because I don't need to declare a #Subcomponent if I don't have to. However, now I want to inject a custom view into my MainActivity subcomponent, and I'm having issues going back to the old dagger.android way of doing things.
So far, I replaced the ContributesAndroidInjector with the old way, like this
#Binds
#IntoMap
#ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity>
bindMainActivityInjectorFactory(MainActivitySubcomponent.Builder builder);
and I created a new MainActivitySubcomponent that looks like this
#PerActivity
#Subcomponent
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
The module pretty much stayed the same. The problem is that I'm getting lots of errors that say that Activity and Context are bound multiple times.
So my 2 questions are:
Is it not possible to have #Binds with Activity and Context if they are scoped so that I can keep things totally decoupled?
Is there an alternative to do view injections using the latest from dagger.android?
Any suggestions will be much appreciated.
Related
Im learning and newer to Dagger2 and stuck in the issue where I need to use both #Provides and #Binds in a Module. but its giving error
[Dagger/DependencyCycle] Found a dependency cycle:
public interface CarComponent{
^
com.stupido.daggertutorial.Engine is injected at
com.stupido.daggertutorial.CarModule.bindsEngine(arg0)
com.stupido.daggertutorial.Engine is injected at
com.stupido.daggertutorial.CarModule.providesCar(engine, …)
com.stupido.daggertutorial.Car is requested at
com.stupido.daggertutorial.CarComponent.getCar()
before I was using #Provides it worked fine but with combination I get above issue.
Component
#Component(modules = CarModule.class)
public interface CarComponent{
//constructor injection
Car getCar();
}
Module
#Module
public abstract class CarModule {
#Binds
abstract Wheels bindsWheels(Wheels wheels);
#Binds
abstract Engine bindsEngine(Engine engine);
#Provides
static Car providesCar(Engine engine,Wheels wheels){
return new Car(wheels,engine);
}
}
Activity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CarComponent carComponent = DaggerCarComponent.create();
Car car = carComponent.getCar();
}
}
#Binds is usually for the case when you have some inherited class and its interface and you want dagger to know this and be able to inject the interface. Usually you'd have
#Binds
abstract Engine bindsEngine(DieselEngine dieselEngine);
and then you can just inject engine without knowing the implementation detail (it's diesel engine).
In your case, if you remove both #Binds methods, it should start working (if there's no other issue).
You are using #Binds in the wrong way. Please read their documentation.
A #Binds method:
Must have a single parameter whose type is assignable to the return type. The return type declares the bound type (just as it would for a #Provides method) and the parameter is the type to which it is bound.
You have to change #Binds to #Provides.
In Dagger, You can inject your activity as View in Presenter, Please follow below example,
Splash Module
#Module
class SplashModule {
#Provides
fun provideXUseCase(
xRepository: XRepository
) = XUseCase(xRepository)
#Provides
fun provideSplashPresenter(
view: SplashView,
xUseCase: XUseCase
): SplashPresenter = SplashPresenter(
view,
xUseCase
)
}
View Module
#Module
abstract class ViewModule {
#Binds
abstract fun provideSplashView(activity: SplashActivity): SplashView
}
Activity Module
#Module
abstract class ActivitiesModule {
#ContributesAndroidInjector(modules = [SplashModule::class, ViewModule::class])
abstract fun bindSplashActivity(): SplashActivity
}
I tried to find how to do it in ToothPick, but could not find any official document or blog post!
Thanks 🙏
Yes, you can do it in a very similar fashion.
You can have a module that binds the view interface to an InstanceProvider (which you can define as a lambda)
In the presenter you declare the View as #Inject and then call Toothpick.inject() as part of the initialization.
The only tricky part is to take care of the scope tree. When I did this I used an Application scope as well as an Activity scope and only declared the bind of the View at Activity level, then the presenter calls inject with the same scope and it all should work fine.
The Activity scope is needed so we override the InstanceProvider each time a new Activity is created (and the View has a new reference and I recall the old one was cached if the scope was the same)
I hope I explained it properly. It wasn't obvious how to do it, but once all the pieces are in place it makes sense.
ArticlesContract.Presenter is a new instance in Adapter, which is different with ArticleListFragment, so my data was lost ! I have no idea why I got two different instances:
#Module
public class ArticleListFragmentModule {
#Provides
ArticlesContract.Presenter provideArticlesPresenter(ArticlesPresenter presenter) {
return presenter;
}
}
public class ArticleListFragment extends DaggerFragment implements ArticlesContract.View {
#Inject
ArticlesContract.Presenter mPresenter; //one instance
}
public class ArticlesAdapter extends RecyclerView.Adapter<ArticleViewHolder> {
#Inject
ArticlesContract.Presenter mPresenter; //another different instance
}
2018-05-16 UPDATED: this issues is fixed by following #Fred answer : lack a scope and managing this scope:
#Scope
#Documented
#Retention(RetentionPolicy.RUNTIME)
public #interface ArticlesScope {
}
#Module
public abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = ArticleListFragmentModule.class)
#ArticleListScope
abstract ArticleListFragment bindArticleListFragment();
}
#Module
public class ArticleListFragmentModule {
/**
* Provide dependency for interface.
* Interface cannot be annotated with #Inject, otherwise it will cause, error: ArticlesContract.Presenter cannot be provided without an #Provides- or #Produces-annotated method.
*/
#Provides
#ArticleListScope
ArticlesContract.Presenter provideArticlesPresenter(ArticlesPresenter presenter) {
return presenter;
}
}
Scoping with #ContributesAndroidInjector, refer to Dagger 2 Annotations: #Binds & #ContributesAndroidInjector
This is because you lack a scope and managing this scope. You need to create a scope yourself or use one provided by dagger already. Here since it's a presenter it can be the Reusable scope I guess or Singleton. However, Singleton has a performance impact that might not be desirable.
The next important thing is that you need to understand that while the component's instance is the same the provided binding will be the same (excluding the case of the Reusable scope). In other words, scopes provide a way of telling dagger - "while this component is alive and it's scope is X then all instances scoped with X will be the same". Here's what I mean in code:
#Scope
#Documented
#Retention(RUNTIME)
public #interface PresenterScope {}
#Module
public class ArticleListFragmentModule {
#Provides
#PresenterScope
ArticlesContract.Presenter provideArticlesPresenter(ArticlesPresenter presenter) {
return presenter;
}
}
I don't know how you've set up your component, but you'd have to annotate it also with the PresenterScope. Now, it's just a matter of making sure that when you inject ArticleListFragment and ArticlesAdapter you'll be using the same instance of the component. If you rebuild the component than the instance of the presenter will be different.
Remember that Reusable is a bit different, but it should suit your needs here since the presenter should hold no state.
Hope this helps
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.
I'm using the new Dagger2 (ver 2.11) and I'm using the new features like AndroidInjector, and ContributesAndroidInjector. I have an activity subcomponent,
#Module
abstract class ActivityBuilderModule {
#ContributesAndroidInjector(modules =
{UserListModule.class, MainFragmentModule.class})
#ActivityScope
abstract MainActivity bindsMainActivity();
}
#Module
public abstract class MainFragmentModule {
#ContributesAndroidInjector
#FragmentScope
#FragmentKey(UserListFragment.class)
abstract UserListFragment bindsUserListFragment();
}
And the UserListModule provides dependencies for the fragment. Some of the dependencies I just want to bind the instances , and return , like
#Binds
#ActivityScope
abstract UserListView mUserListView(UserListFragment userListFragment);
Instead of simply just return the dependency , like
#Provides
#ActivityScope
UserListView mUserListView(UserListFragment userListFragment){
return userListFragment;
}
My module contains some #Provides methods as well. Can we use both #Binds and #Provides methods in the same module? I tried as shown below
#Module
public abstract class UserListModule {
#Provides
#ActivityScope
UserListFragment mUserListFragment() {
return new UserListFragment();
}
#Binds
#ActivityScope
abstract UserListView mUserListView(UserListFragment userListFragment);
// other provides and binds methods...
......
.....
}
And it its throwing error
Error:(22, 8) error: dagger.internal.codegen.ComponentProcessor was unable to process this interface because not all of its dependencies could be resolved. Check for compilation errors or a circular dependency with generated code.
Is there any way to do this?
#Binds and #ContributesAndroidInjector methods must be abstract, because they don't have method bodies. That means that they must go on an interface or abstract class. #Provides methods may be static, which means they can go on abstract classes and Java-8-compiled interfaces, but non-static ("instance") #Provides methods don't work on abstract classes. This is explicitly listed in the Dagger FAQ, under the sections "Why can’t #Binds and instance #Provides methods go in the same module?" and "What do I do instead?".
If your #Provides method doesn't use instance state, you can mark it static, and it can go onto an abstract class adjacent to your #Binds methods. If not, consider putting the bindings like #Binds and #ContributesAndroidInjector into a separate class--possibly a static nested class--and including that using the includes attribute on Dagger's #Module annotation.
A little addition to Jeff's solution above:
you may create inner interface instead of static inner class, like this:
#Module(includes = AppModule.BindsModule.class)
public class AppModule {
// usual non-static #Provides
#Provides
#Singleton
Checkout provideCheckout(Billing billing, Products products) {
return Checkout.forApplication(billing, products);
}
// interface with #Binds
#Module
public interface BindsModule {
#Binds
ISettings bindSettings(Settings settings);
}
}
In kotlin, you can leverage companion object
#Module
abstract class MyDaggerModule {
#Binds
abstract fun provideSomething(somethingImpl: SomethingImpl): Something
companion object {
#Provides
fun provideAnotherThing(): AnotherThing {
return AnotherThing()
}
}
}
This is other type solution: Add modules to other module after that you can call top module in your component interface. It can be more efficiency because you can use abstract and static.
Details and examples are below:
For example, we have an component interface and two modules such as ComponentClasses, Module_ClassA and Module_ClassB.
Module_ClassA is:
#Module
public class Module_ClassA {
#Provides
static ClassA provideClassA(){
return new ClassA();
}
}
Module_ClassB is:
#Module
abstract class Module_ClassB {
#Binds
abstract ClassB bindClassB(Fragment fragment); //Example parameter
}
So now, we have two models. If you want use them together, you can add one of them to other. For example: You can add Module_ClassB to Module_ClassA:
#Module(includes = Module_ClassB.class)
public class Module_ClassA {
#Provides
static ClassA provideClassA(){
return new ClassA();
}
}
Finally, you do not need to add both modules to your component class. You can only add your top module on your component class, like that:
ComponentClasses is:
#Component(modules = Module_ClassA)
public interface ComponentClasses {
//Example code
ArrayList<CustomModel> getList();
}
However, you should be careful because you need to add your top module. Thus, Module_ClassA added on ComponentClasses interface.