Dagger2- Switching Dependencies for different flavours: Android Studio - android

I am using Dagger 2 and would like to use it to inject different dependencies for different build flavours in Android Studio.
public class DemoApplication extends Application{
AppComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerAppComponent.builder().module(new Module()).build();
}
}
#Module
public class Module {
#Provides
#Singleton
public ClassA provideClassA(){
return new ClassA();
}
}
#Component (modules = {Module.class})
public interface AppComponent {
ClassA getClassA();
}
Now lets say I want object of ClassA to be returned for prod flavour but an object of ClassB (which extends from ClassA) in the debug flavour.

Use flavor-specific source sets
For example, let's consider mock and production flavors. Create mock and production directories and put them alongside main, place a separate class named Module in both mock and production directories (the package must also be the same). This way, when you're switching build variants, the compiler will refer to either the Module class in mock or production flavor, allowing you to inject different implementations depending on the flavor.
Check out this reference on source sets: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Sourcesets-and-Dependencies
This answer might also be helpful: How the flavor-specific variants are working?

Related

Why a class can't find #Named provider from a Hilt module?

I am migrating the application from Dagger2 to Hilt, and I started it from creating a single activity that uses Hilt
I wrote the inject annotation for one of the dependencies inside the activity
I have the provider method(it has a return type) in my HiltApplicationModule
configService is one of the dependencies provideRemoteConfig needs, so for that I also have a provider named method
Moreover, one of the dependencies that RemoteConfig also uses - it uses that same configService that we need to inject
When I run the build, I receive the error
#javax.inject.Named("config_service_merchant") com.example.configapp.ConfigService cannot be provided without an #Provides-annotated method and
public interface ApplicationComponent extends AndroidInjector {
^
#javax.inject.Named("config_service_merchant") com.example.configapp.ConfigService is injected at
At this point, I am confused why it can't find it if I have the method
My assumption that it tries to find it in Dagger2 modules and application components that I still have in the project, but I am not sure.
Why does configService's provider cannot be found to be injected in one of the classes(particularly AnalyticsConfig)?

Migrating to Hilt from Dagger2

I am trying to follow the Hilt migration guide here:
https://dagger.dev/hilt/migration-guide.html
And have annotated all my modules with:
#InstallIn(SingletonComponent::class)
However I am running into issues with my "Contributor" Modules for services, fragments and activities.
I have one module for each,
#Module
#InstallIn(SingletonComponent::class)
abstract class FragmentContributorModule {
#ContributesAndroidInjector
internal abstract fun contributeMyFragment(): MyFragment
}
#Module
#InstallIn(SingletonComponent::class)
abstract class ActivityContributorModule {
#ContributesAndroidInjector
internal abstract fun contributeMyActivity(): MyActivity
}
#Module
#InstallIn(SingletonComponent::class)
abstract class ServiceContributorModule {
#ContributesAndroidInjector
internal abstract fun contributeMyService(): MyService
}
During compile I am getting errors for each one of the "contribute" functions:
com.test.ActivityContributorModule_ContributeMyActivity$defaultsDebug is missing an #InstallIn annotation. If this was intentional, see https://dagger.dev/hilt/compiler-options#disable-install-in-check for how to disable this check.
I have also tried to use ServiceComponent::class, FragmentComponent::class and ActivityComponent::class for each Module with no luck. I am trying to migrate the project in pieces so I don't think I can remove these until everything is upgraded to Hilt.
Any ideas?
The answer is here:
Warning: Modules that are not annotated with #InstallIn are not used
by Hilt. Hilt by default raises an error when unannotated modules are
found, but this error can be disabled.
Was not 100% clear to me at first, but the contributor modules I have for services/fragments/activities are only used with Dagger, not Hilt. So if you are trying to migrate you project in pieces you can leave those module as is until you start to provide Hilt entry points for your services/fragments/activities. However if you do that, you will need to tell Hilt to ignore the error it throws for missing #InstallIn.
More info on how to disable that here:
https://dagger.dev/hilt/compiler-options.html#disable-install-in-check
By default, Hilt checks #Module classes for the #InstallIn annotation
and raises an error if it is missing. This is because if someone
accidentally forgets to put #InstallIn on a module, it could be very
hard to debug that Hilt isn’t picking it up.
This check can sometimes be overly broad though, especially if in the
middle of a migration. To turn off this check, this flag can be used:
-Adagger.hilt.disableModulesHaveInstallInCheck=true.
Alternatively, the check can be disabled at the individual module
level by annotating the module with #DisableInstallInCheck.

Koin - Ensure that all "by inject" are valid

I work on an Android project using multiple Project Flavors, and we use Koin to inject the appropriate dependencies based on the current flavor.
We already use the checkModules Gradle task (described here : https://start.insert-koin.io/#/getting-started/testing?id=checking-your-modules) in order to ensure that our dependency tree is valid.
However, it seems that there is a use case missing.
Let's say I want to inject an InterfaceA in my Activity. I would write the following code :
class MyActivity : Activity() {
private val interfaceA_Impl: InterfaceA by inject()
...
}
Koin requires the implementation of InterfaceA to be provided in a module, as such :
val myModule = module {
single<InterfaceA> { MyInterfaceImpl() }
}
In my project, each implementation is "flavor-specific".
My question is :
Is there a way to ensure that ALL by inject targets are valid ? In other words, to ensure that all interfaces that I am trying to inject have valid implementations ? Currently, if an implementation is forgotten, the app crashes during runtime, and I would like to know about it sooner (maybe during unit tests, at the same time checkModules is ran ?)
Thanks a lot !

How to easily replace production Hilt modules with fakes in all tests

My Android production is code full of Hilt modules that install various production implementations:
#Module
#InstallIn(ApplicationComponent.class)
public abstract class TimeModule {...}
#Module
#InstallIn(ApplicationComponent.class)
public abstract class DatabaseModule {...}
In all my instrumented tests, I would like to replace those bindings with fakes. My test codebase includes modules that bind fake implementations, but having two modules provide the same class obviously causes compile-time errors.
The Hilt documentation recommends using #UninstallModule(), but that means I'd have to add UninstallModule for every single production module in every single test. That seems like the wrong approach.
How would one normally replace production modules with fake modules? And is there a way to install modules from another module like Guice does, so I could remove #InstallIn from all my production modules and instead simply have one ProductionModule that installs all the individual modules? That would make it easier to just uninstall one module in tests.
How would one normally replace production modules with fake modules?
Probably how it's normally done, is like the documentation said with the UninstallModule annotation. But here is an alternative, which I like to use, using build flavors:
I like to organize my project, so there are mock and live flavors. And there are 3 folders inside my app module: src/main/kotlin with Activities, Fragments etc..., src/mock/kotlin where my fake bindings live, and finally src/live/kotlin where my real production bindings live.
Here's the relevant config from my app level build.gradle.kts:
android {
productFlavors {
flavorDimensions("environment")
register("mock") {
dimension = "environment"
}
register("dev") {
dimension = "environment"
}
register("prod") {
dimension = "environment"
}
sourceSets {
getByName("mock").java.srcDir("src/mock/kotlin")
getByName("dev").java.srcDir("src/live/kotlin")
getByName("prod").java.srcDir("src/live/kotlin")
}
}
}
Project structure overview
Inside the live InteractorModule:
#Module
#InstallIn(ApplicationComponent::class)
abstract class InteractorModule {
#Binds
abstract fun bindTodoInteractor(interactor: TodoInteractorImpl): TodoInteractor
}
Inside the FakeInteractorsModule:
#Module
#InstallIn(ApplicationComponent::class)
abstract class InteractorModule {
#Binds
abstract fun bindTodoInteractor(interactor: TodoInteractorFake): TodoInteractor
}
So now you can use the build variant tab to change between the real and the mock implementations of your interfaces. So if you want to use your fakes inside your instrumentation tests use the mock flavor while running the tests.
One upside of this method, is that by changing the build variant, you can swich between your instrumentation tests using your fakes to using the live implementations. Conversly, you can use your fake implementations inside the actuall application, which can be nice, if you just want to try out the app with mock data.
I hope this helped a bit to at least promote some ideas, on how you can solve this "two implementations for one interface" dilemma!

How can I change a Dagger module's injections by gradle flavor?

I have an Android/gradle project with two flavors that uses Dagger. These flavors have several activities in common, but each flavor has a couple of activities that are unique to the flavor. So, for one flavor:
#Module(injects = {
CommonActivity1.class,
CommonActivity2.class,
Flavor1Activity.class,
})
public class MyModule { ... }
And for the other:
#Module(injects = {
CommonActivity1.class,
CommonActivity2.class,
Flavor2Activity.class,
})
public class MyModule { ... }
The modules are identical except for the classes they inject. I'm currently just copying code between the two MyModule.java files, which are placed in the appropriate per-flavor directory.
I'd like to find a solution that avoids copy/pasted code between two identical modules, and ideally prevents needing to specify the common activities twice
I don't care whether the solution is in Java or Gradle. If there's a way to generate the annotation in Gradle, that's fine. If there's a way to refactor the injects list in Java, that's fine too. I'm also open to ideas for reorganizing my approach to Dagger to avoid the problem entirely.
I think your solution here is addsTo.
If the content of the modules are the same then include the basic module implementation in your default source directory. Then in each of the flavour implementations create your flavour specific modules.
Use addsTo = BaseModule.class in your module annotations.
Using this method you only need to define the annotations in the module and can reuse the base implementation.
You end up with something like this
#Module (
library = true
, complete = false
)
public class BaseModule {}
#Module (
library = true
, complete = false
, addsTo = BaseModule.class
, injects = { ... }
)
public class FlavourOneModule {}
#Module (
library = true
, complete = false
, addsTo = BaseModule.class
, injects = { ... }
)
public class FlavourTwoModule {}

Categories

Resources