Prefer to Use @Binds over @Provides in Dagger

If you didn’t know, here are some hidden costs when using @Provides in your Dagger modules.

Increase in Method Count

In the previous Basic and Advanced Dagger tutorials that we had, we’re constantly using @Provides to annotate the methods of our modules.

Let’s take this code snippet for example found in our Basic Dagger tutorial.

@Module
class RepositoryModule {

    @Provides
    @Singleton
    fun providesUserRepository(api: Api): UserRepository {
        return UserRepositoryImpl(api)
    }
}

Highlight the method name providesUserRepository, press right click and select Find Usages.

You’ll see something like this:

dagger binds and provides difference

Right click RepositoryModule_ProvidesUserRepositoryFactory and select Jump to Source.

public final class RepositoryModule_ProvidesUserRepositoryFactory implements Factory<UserRepository> {
  private final RepositoryModule module;

  private final Provider<Api> apiProvider;

  public RepositoryModule_ProvidesUserRepositoryFactory(RepositoryModule module,
      Provider<Api> apiProvider) {
    this.module = module;
    this.apiProvider = apiProvider;
  }

  @Override
  public UserRepository get() {
    return providesUserRepository(module, apiProvider.get());
  }

  public static RepositoryModule_ProvidesUserRepositoryFactory create(RepositoryModule module,
      Provider<Api> apiProvider) {
    return new RepositoryModule_ProvidesUserRepositoryFactory(module, apiProvider);
  }

  public static UserRepository providesUserRepository(RepositoryModule instance, Api api) {
    return Preconditions.checkNotNull(
        instance.providesUserRepository(api),
        "Cannot return null from a non-@Nullable @Provides method"
    );
  }
}

You don’t need to dig deep into it. This factory is basically responsible for creating a UserRepository.

What you need to know is that for each method annotated with @Provides in our modules, Dagger will create a factory for EACH method. I repeat again - EACH METHOD. If you have 10 repository classes, Dagger will generate 10 factory class for each method in your module like the one above.

How much more when you start using Subcomponents and some of it’s modules provides the same classes?

Android has a max method count of ~65k and the methods of these generated factory classes are COUNTED.

More Method Calls to Do Just to Get the Object

@Binds methods are a drop-in replacement for Provides methods that simply return an injected parameter.
Prefer @Binds because the generated implementation is likely to be more efficient.

You can find this snippet in the Dagger’s @Binds documentation.

This is the difference of the method calls between them:

You might be wondering - “Wait, isn’t it the same thing? It still generated a factory.”

The difference between them is that RepositoryModule_ProvidesUserRepositoryFactory can only be used as it’s prefix suggests in the RepositoryModule and the component to where it’s declared.

So if you start using Dagger Subcomponents where you declare a module for each of them which is what we did in our Advanced Dagger Tutorial, and for example you provide a UserRepository for each Subcomponent module, you’ll have these redundant factory classes which causes the problem stated before - “Increase in method count”.

On the other hand, UserRepositoryImpl_Factory is REUSABLE. It’s a factory for the object itself and NOT for the module. Different Dagger components/subcomponents can use this factory to ask a for UserRepository.

And it lessens the overhead of method calls because we directly interact with the factory of the object and no more traversing to different classes just to get an object.

Then how do we use @Binds?

How to Use @Binds

There are some rules that you need to follow:

  1. The module class must be abstract
  2. The method must be abstract and has no method body
  3. There should only be one method parameter
  4. The parameter must be assignable to the return type
  5. The parameter must have an @Inject annotated constructor

Let’s take this code snippet for example:

@Module
class RepositoryModule {

    @Provides
    @Singleton
    fun providesUserRepository(api: Api): UserRepository {
        return UserRepositoryImpl(api)
    }
}

Change it to this:

@Module
abstract class RepositoryModule {

    @Binds
    @Singleton
    abstract fun providesUserRepository(userRepository: UserRepositoryImpl): UserRepository
}

And make sure your UserRepositoryImpl class’ constructor is annotated with @Inject:

class UserRepositoryImpl @Inject constructor(private val api: Api) : UserRepository {
    ...
}

Build your app and it should work as it was before.

If you’ve found this helpful, subscribe to my newsletter below to be updated with the latest posts and get a free book on the 7 ways to become a really good Android developer!👇

7 ways to become a really good Android developer