Android multi-SDK project debug build - android

How to add a debug-mode specific functionality into an Android app which is broken into several SDKs?
Lets say I need to add an OkHttp interceptor to all OkHttp clients to all SDKs. But it has to be added only into the app's debug build. However, when the app is broken into several SDKs, they are published as release builds even if the main app is built in the debug mode. Thus, the debug-specific interceptors inside the SDKs would not be added to the OkHttp clients.
How to overcome this issue? I was thinking to take advantage of dependency injection (Koin) and pass Build type information from the app where Koin is started, but not sure Koin supports this feature.

You can select what build variant is active of each of the modules of your application.
https://developer.android.com/studio/build/build-variants

You can use getAll() in Koin for your purposes.
moduleb:
Domain:
interface Interceptor
class InterceptorFactory(val interceptors: List<Interceptor>) // Here you have a list of all interceptors from all modules
class HeadersInterceptor : Interceptor
Koin:
object BKoin {
val network2: Module
get() = module {
single<HeadersInterceptor>() bind Interceptor::class
single<InterceptorFactory> {
InterceptorFactory(getAll<Interceptor>())
}
}
}
module app:
define your objects
class OtherInterceptor : Interceptor
declare them
object AKoin {
val network1: Module
get() = module {
single<OtherInterceptor>() bind Interceptor::class
}
}
init Koin
startKoin {
val app = modules(
listOf(
BKoin.network2,
AKoin.network1
)
)
Log.d("TUT", "${app.koin.get<InterceptorFactory>().interceptors.map { it::class.simpleName }}")
}
Gradle:
dependencies {
implementation project(':moduleb')
}

Related

Share definition of CoroutineExecutorExtension

I have created two classes:
#ExperimentalCoroutinesApi
open class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance()
.setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
override fun postToMainThread(runnable: Runnable) = runnable.run()
override fun isMainThread(): Boolean = true
})
}
override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
}
}
and
#ExperimentalCoroutinesApi
class CoroutineExecutorExtension : InstantExecutorExtension() {
private val testCoroutineDispatcher = TestCoroutineDispatcher()
override fun beforeEach(context: ExtensionContext?) {
super.beforeEach(context)
Dispatchers.setMain(testCoroutineDispatcher)
}
override fun afterEach(context: ExtensionContext?) {
super.afterEach(context)
Dispatchers.resetMain()
testCoroutineDispatcher.cleanupTestCoroutines()
}
}
I'm using these classes to write unit tests for method that use coroutines and livedata.
The problem I have is that I don't know how to share these classes in different modules.
If I define them in the test folder then I can build the classes but they are not visible in other modules.
If I define in the main folder there are compile time errors.
What can I do to avoid having to define these classes in all the modules?
UPDATE
To follow #Sam suggestion I have created a module that contains the two classes in the folder:
.\unittest\src\testFixtures\java\com\name\terminal\unittest
The build.gradle file of this module uses the plugins:
plugins {
id 'com.android.library'
id 'kotlin-android'
id 'java-test-fixtures'
}
In the module where I want to use the CoroutineExecutorExtension I have imported the project of the unittest module with:
testImplementation(testFixtures(project(":unittest")))
The problem is that the classes in the testFixtures folder are not visiible.
EDIT
This method does not work for an Android library. I can't use a java library because I need to be able to include Androidx dependencies.
If you're using Gradle, the test fixtures plugin is an appropriate solution. The plugin is built in, so you can simply add java-test-fixtures to your plugins block.
plugins {
`java-test-fixtures`
}
This automatically creates a new testFixtures source set, alongside the existing main and test source sets that you already have. Code that you write inside src/testFixtures/kotlin will be visible from your tests, and will have access to everything in main. Anything that the test fixtures need (for example, dependencies on kotlinx-coroutines or your test framework) should be added as dependencies of the test fixtures.
You can also also add dependencies on test fixtures from another module. For example, if module-b needs to use the test fixtures from module-a, then in the Gradle build script for module-b you could add:
dependencies {
testImplementation(testFixtures(project(":module-a")))
}
The examples I've given are for Kotlin build scripts, but you can find the Groovy equivalents in the linked documentation.

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!

Dagger 2 using classes generated by another library

I have a homemade library that generates DataMapper classes.
They are generated with #Singleton and #Inject annotations to be able to inject them where i need them.
But where it doesn't work is when Dagger tries to create the dependency tree, this error shows :
:data:kaptGenerateStubsDebugKotlin
e: /Users/me/myproject/data/build/tmp/kapt3/stubs/debug/com/myproject/data/di/DataComponent.java:11: error: [Dagger/MissingBinding] error.NonExistentClass cannot be provided without an #Inject constructor or an #Provides-annotated method.
public abstract com.myproject.domain.repository.ContentRepository contentRepository();
^
error.NonExistentClass is injected at
com.myproject.data.repository.ContentDataRepository.<init>(…, myGeneratedDataMapper, …)
com.myproject.data.repository.ContentDataRepository is injected at
com.myproject.data.di.module.DataModule.contentRepository(contentDataRepository)
com.myproject.domain.repository.ContentRepository is provided at
com.myproject.data.di.DataComponent.contentRepository()
:data:kaptDebugKotlin
:data:kaptDebugKotlin FAILED
Involved classes are :
DataModule (module for dagger)
#Module
class DataModule {
#Provides
#Singleton
fun contentRepository(contentDataRepository: ContentDataRepository): ContentRepository = contentDataRepository
}
DataComponent (component for dagger):
#Singleton
#Component(modules = [DataModule::class])
interface DataComponent {
fun contentRepository(): ContentRepository
}
ContentDataRepository
#Singleton
class ContentDataRepository #Inject constructor(
private val myGeneratedDataMapper: MyGeneratedDataMapper
) : ContentRepository {
...
}
MyGeneratedDataMapper
#Singleton
class MyGeneratedDataMapper #Inject constructor() {
...
}
The thing is, if i disable kapt of dagger dependency in gradle.build, then build, then enable it, then build, it works.
If i do a clean + build, it doesn't work, same error.
I want to make it work in one row.
I don't know if you are using AS3.2 or AS3.3 with androidX artifacts or not but Maybe this is the case with you too.
so when i migrated to androidX artifacts in AS3.2 i got hit with bunch of NonExistentClass errors ends the build with
kaptGenerateStubsDebugKotlin
:data:kaptDebugKotlin
:data:kaptDebugKotlin
I finally found out that it has something to do with Dagger itself and degraded the version from 2.17 to 2.16 now the latest version of Dagger2 is 2.18 which i can't use due to this bug / feature [they forgot about].
Update:
i found the solution and it just came today so here is the issue tracker link:
https://issuetracker.google.com/issues/115738511
so the bug was not in the Dagger but it was with Jetifier and i totally ignored the fact that it was set enabled during migration
here's the solution i copied from the link:
Sorry jetifier beta01 was not binary compatible with alpha10.
We have published beta02 that should fix this issue.
Please try:
buildscript { dependencies {
classpath 'com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta02' } }
You're probably not going to like my answer but the order is kinda random.
Look at this thread for some more explaining and maybe some more guidance but, if you want to verify you are running first look at Gradle plugins and how to use them

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