Dagger Inject for Integration Tests - android

So up to Dagger 2.11 I've been able to construct TestComponent's and Modules to enable key components to be injected into integration tests. This is great for Api tests and objects with heavy component requirements
Typically I would have code like this:-
class SpotifyApiTest {
lateinit var spotifyApi : SpotifyApi
#Inject set
lateinit var spotifyHelper : SpotifyIOHelper
#Inject set
#Before
fun setup() {
var context = InstrumentationRegistry.getInstrumentation().context
val testAppComponent = DaggerSpotifyTestComponent.builder()
.spotifyApiModule(SpotifyApiModule(context))
.build()
testAppComponent.inject(this)
}
#Test
......
}
N/B remember to add the following to your gradle build file
kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
This approach works extremely well up to Dagger 2.11 but after that version Modules with parameterized constructors do not work preventing context being supplied let alone the application. So how can I use the new AndroidInjection() functionality for integration tests with the Dagger 2.16 for example?

I've come up with the following approach to inject the context into the modules for dagger 2.11+ so its a start, I've still no idea how to handle application from the integration test perspective.
#Singleton
#Component(modules = arrayOf(SpotifyAccountsModule::class,SpotifyApiModule::class))
interface SpotifyTestComponent {
fun inject(test: SpotifyApiTest)
fun inject(test: SpotifyAccountApiTest)
#Component.Builder
interface Builder {
#BindsInstance
fun create(context: Context): Builder
fun build(): SpotifyTestComponent
}
}
This will make context available as a parameter for providers and should help others when they reach this particular brick wall.

Related

Provide domain-layer UseCase classes with HILT

I am implementing some of the architectural designs from Google I/O's app to my own app, but I have come across something in their app that has created some confusion for me.
They have a domain layer with repository usecases, which I use myself in my apps usually. However, I do have to provide these usecases with dagger in my app. But on Google's I/O app, I cannot find any module that provides these usecases. And when used with viewmodels annotated #HiltViewModel (In my own app), it seems like it works? Somehow these get injected into my viewmodels. I do have to provide all the usecase's dependencies(repositories etc) with Hilt, but I don't have to provide any usecase via Hilt.
Here is example how it looks in my code.
Usecase:
abstract class UseCase<in P, R>(private val coroutineDispatcher: CoroutineDispatcher) {
suspend operator fun invoke(parameters: P): Resource<R> {
return try {
withContext(coroutineDispatcher) {
execute(parameters).let {
Resource.Success(it)
}
}
} catch (e: Exception) {
Timber.d(e)
Resource.Error(e.toString())
}
}
#Throws(RuntimeException::class)
protected abstract suspend fun execute(parameters: P): R
}
Concrete implementation of usecase:
class GetListUseCase #Inject constructor(
private val coroutineDispatcher: CoroutineDispatcher,
private val remoteRepository: RemoteRepository
): UseCase<ListRequest,ItemsList>(coroutineDispatcher) {
override suspend fun execute(parameters: ListRequest): ItemsList{
return remoteRepository.getList(parameters)
}
}
Viewmodel:
#HiltViewModel
class DetailViewModel #Inject constructor(
private val GetListUseCase: getListUseCase
): ViewModel() {
suspend fun getList(): Resource<ItemsList> {
getPokemonListUseCase.invoke(ListRequest(3))
}
}
Example of provided repo:
#Singleton
#Provides
fun provideRemoteRepository(
api: Api
): RemoteRepository = RemoteRepositoryImpl(api)
Remote repo:
#ActivityScoped
class RemoteRepositoryImpl #Inject constructor(
private val api: Api
): RemoteRepository {
override suspend fun getList(request: ListRequest): PokemonList {
return api.getPokemonList(request.limit, request.offset)
}
}
My Question is:
How come this works? Why don't I have to provide usecase' via Hilt? Or is my design wrong and I should provide usecases via Hilt even if this works?
EDIT:
Link to Google I/O domain layer and ui layer if it helps.
Your design is great! And it's not wrong, but yes, you can add some additional context for your use cases if you put them to a separate module and scope them per your needs. Modules exist mostly for cases when you do have your third-party dependencies (the simplest example is OkHTTPClient) or when you have interface -> impl of the interface, or you want to visibly limit/extend lifecycle/visibility of your components.
Currently you're telling Hilt how to provide instances of GetListUseCase by annotating its constructor with #Inject AND Hilt already knows what is "CoroutineDispatcher" (because it's been provided by #Provides in the coroutine module, right?) and what is RemoteRepository (because you're injecting the interface, but beneath the hood you're providing the real implementation of it in the repo module by #Provides).
So it's like saying - give me an instance of the use case class with two constructor dependencies, and both of them are known to Hilt, so it's not confusing.
And if you want to have a scoped binding/component (like explained here) or to mark your use case as a singleton, then you have to create a UseCaseModule and scope your use case components there.

Dagger2 + ActivityInjection + AndroidXTest/Espresso/RoboElectric in library project

I am working on android library module and I want to test the standalone activity in my module. I was following the article https://medium.com/androiddevelopers/write-once-run-everywhere-tests-on-android-88adb2ba20c5 to use roboelectric and androidx test with espresso. I recently introduced dagger 2 to my library project.
With that my Activity looks like this:
class XYZLibBaseActivity : AppCompatActivity(){
#Inject
lateinit var resourceProvider: ResourceProvider
override fun onCreate(savedInstanceState: Bundle?) {
//creating the dagger component
DaggerXYZLibComponent.factory().create(application).inject(this)
super.onCreate(savedInstanceState)
}
}
My component declaration is
#Component(modules = [ResourceProviderModule::class])
interface XYZLibComponent{
#Component.Factory
interface Factory{
fun create(#BindsInstance application: Application):XYZLibComponent
}
fun inject(xyzLibBaseActivity: XYZLibBaseActivity)
}
and dagger module is
#Module
class ResourceProviderModule {
#Provides
fun provideResourceProvider(application: Application): ResourceProvider{
return ResourceProviderImpl(application.applicationContext)
}
}
This works perfectly fine and I don't want the underlying application to use dagger 2.
Now I wan to test my activity without depending on the underlying application or application class. How can I inject mock ResourceProvider in the activity?
One of many options is
create 2 flavors in your gradle config: real and mock
in both flavors, define a boolean buildConfigField flag
In your provideResourceProvider, return a corresponding implementation based on the flag's value

Can not provide context for dagger injection

I'm new in dagger , I want to inject context and network (using retrofit) in my classes .
this is my code so far :
#Module
// Safe here as we are dealing with a Dagger 2 module
#Suppress("unused")
object NetworkModule {
#Provides
#Reusable
#JvmStatic
internal fun provideMainApi(retrofit: Retrofit): MainApi {
return retrofit.create(MainApi::class.java)
}
#Provides
#Reusable
#JvmStatic
internal fun provideRetrofitInterface(): Retrofit {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
return Retrofit.Builder()
.baseUrl(Constants.baseUrl)
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.client(client)
.build()
}
}
#Module
class AppModule(private val app: Application) {
#Provides
#Singleton
fun provideApplication() = app
}
this is my component :
#Singleton
#dagger.Component(modules = arrayOf(AppModule::class, NetworkModule::class))
interface AppComponent {
///for injecting retrofit network
fun injectMain(mainRepository: MainRepository)
#dagger.Component.Builder
interface Builder {
fun build(): AppComponent
fun networkModule(networkModule: NetworkModule): Builder
fun appModule(appModule: AppModule):Builder
}
}
I want to use it in my repository , I've a baseRepository :
open class BaseRepository {
private val injector: AppComponent = DaggerAppComponent
.builder()
.networkModule(NetworkModule)
.build()
init {
inject()
}
private fun inject() {
when (this) {
is MainRepository -> injector.injectMain(this)
}
}
}
when I run the app , I get this error "module.AppModule must be set"
I understand the error and I should provie appMOdule in my base repository but the problem is I don't have any application or context in base repository
how should I fix this ?
the second problem I've is this , I've heard that I should once make the dagger and use it in my entire app and I shouldn't make it every time , it means I should use application for that .
but how can I use injector in an application class , it doesn't make sense
If I were you I would go through this project and see there how everything is done.
https://github.com/google/iosched
Here is explained what this project is about:
https://medium.com/androiddevelopers/google-i-o-2018-app-architecture-and-testing-f546e37fc7eb
Also you can check the Dagger branch ot this projec:
https://github.com/android/architecture-samples
It was all done by Jose Alcerreca and some other guys. These are the guidelines of Google about Android and there will be the answer of most of your questions.
but how can I use injector in an application class , it doesn't make sense There is a DaggerApplication class which you can extend and inject your App class however you want.
And I am telling you all of this, because I think you have learned Dagger from old sources. I would have changed a lot of stuff in your code.
Also I am using Dagger since 1.5 years and I have never ever written such a code like the one in: BaseRepository. I use only and only #Inject annotations and that is all throuhg my classes which are not Dagger related. Creating a Component in a Repository? -> Never!
This is how your classes should end up everywhere.
class Repostory #Inject constructor(
private val dependency1: Dependency1
) {}
class Activity {
#Inject lateinit var dependency2: Dependency2
}

Dagger 2 and dependency injection hell?

How do you use dagger from Kotlin?
I've been in a loop of fixing one compile error and moving to another and at the end I get back to step 1
Here is all I need:
AppDependencies
GenericActivityDependencies
PerActivityDependency
Here are my main dependencies:
App
#Module
class ApplicationModule(private val application: Application) {
#Provides
#Singleton
fun provideContext(): Application = this.application
}
#Singleton
#Component(modules = [ HttpModule::class, ApplicationModule::class ])
interface AppComponent {
val app: Application
}
Why do I need to once provide the dependency in the module and another time define it in the component?
Activity Module
#Module
class ActivityModule(private val activity: Activity) {
#PerActivity
#Provides
#ActivityContext
fun provideContext(): Context = activity
}
#Component(modules = [ActivityModule::class], dependencies = [AppComponent::class])
#ActivityContext
interface ActivityComponent {
fun inject(activity: MainActivity)
}
HomeModule
#Module
class LandingModule {
#PerActivity
#Provides
fun provideSomethig(): Something {
return Something()
}
}
#SomeActivity
#Subcomponent(modules = [LandingModule::class])
interface LandingSubcomponent {
val something: Something
}
By this point, I have written more code than there needs to be in my whole activity.
I get errors like can't inherit from a scopes component
Can't generate Dagger gencode
Subcomponent needs a different scope
How do I achieve this?
Is there a better di for kotlin?
Is there a sample somewhere I could follow that has per activity module?
I understand your frustrations. I have been there before and it took me quite some time to understand dagger myself. Just a quick demo/tutorial.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun context(): Context
}
#Module
class AppModule(private val application: Application) {
#Provides
#Singleton
fun provideApplication(): Application= application
}
The component is the interface to the container. Anything defined in here can be accessible if you are able to instantiate your container successfully. Also, it is the interface to other containers/components. This means that if you want to expose something outside your container, you define it here. Therefore,
Why the heck do I need to once provide the dependency in the module
and another time define it in the component. This is plain stupid.
is not always true. You don't need to define anything in your component if you don't want anything to expose outside. An alternative to exposing would be injecting.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(activity: MainActivity)
}
You are not exposing anything here but you can still get the activity context from the container thru inject.
Now let's proceed to scoping.
Scoping is the way to provide 'local singletons' inside your container. A scoped dependency will be created only once inside the container. An example is your PerActivity scope. A scoped component will only accept a module that is scoped with the same Scope. For example:
#PerActivity
#Component(dependencies = [AppComponent::class],
modules = [ActivityModule::class])
interface ActivityComponent{
fun inject(activity: MainActivity)
}
The corresponding module should only be scoped with PerActivity as well.
class ActivityModule(activity:Activity) {
#PerActivity
#Provides
fun provideActivity() = activity
}
Any other scope defined in your module that is not the same scope as your intended component will result in a compile error. Multiple scopes is not allowed as well.
As for the component dependencies, you can use use dependencies or subcomponents. If dependencies is used, any dependency that is required by the child must be exposed by the parent. In our case above, if the ActivityComponent requires the activity context, the AppComponent must define a function that returns it. In subcomponents, just define your subcomponent in your component and the dependencies will be resolved internally.
I have written a small guide to learning dagger 2. If you are interested, you can go check it out.
https://medium.com/tompee/android-dependency-injection-using-dagger-2-530aa21961b4
https://medium.com/tompee/dagger-2-scopes-and-subcomponents-d54d58511781
Why the heck do I need to once provide the dependency in the module and another time define it in the component. This is plain stupid.
I agree, but don't be discouraged by that fact, because once you learn to master it you'll learn to appreciate it and really take advantage. I've been using version 2.2 for a while without any issues. I've only needed to define one annotation, add a pair of additional dependencies (AutoDagger among them, which takes care of that component feature), and use the following structure:
/* Dagger */
implementation "com.google.dagger:dagger:2.2"
// Fix: github.com/rharter/auto-value-gson/issues/43#issuecomment-219994018
kapt 'com.squareup:javapoet:1.9.0'
kapt "com.google.dagger:dagger-compiler:2.2"
compileOnly 'org.glassfish:javax.annotation:10.0-b28'
/* Autodagger */
kapt "com.github.lukaspili.autodagger2:autodagger2-compiler:1.1"
implementation "com.github.lukaspili.autodagger2:autodagger2:1.1"
DaggerScope.java
#Retention(RetentionPolicy.RUNTIME)
#Scope
#interface DaggerScope {
}
YourApp.kt
#AutoComponent(modules = [YourApp.Module::class])
#AutoInjector
#DaggerScope
class YourApp : Application() {
...
#Singleton #dagger.Module
inner class Module(private val app : YourApp) {
#Provides #AutoExpose(YourApp::class) fun application(): Application = app
#Provides #AutoExpose(YourApp::class) fun context(): Context = app
...
// Stuff like your database or base service can go here
}
}
SomeActivity.kt
#AutoComponent(dependencies = [YourApp::class],
modules = [SomeActivity.Module::class]) // you are free to add other modules here
#AutoInjector
#DaggerScope
class SomeActivity : AppCompatActivity() {
...
#dagger.Module
inner class Module() {
#Provides #AutoExpose(SomeActivity::class) fun something(): Something {
return some way of creating Something
}
/* specific deps for SomeAcitivity's actions, like specific services.
You can also access DAOs as you've got access to the DB */
}
}
You can also mimic this structure with a Fragment instead of an Activity.
Hope this helps you!

How to avoid circular dependency with Dagger 2?

I have the following Module :
#Module
class HomeModule(private val context: Context) {
#Provides
fun provideContext() = context
#Provides
fun provideHomeUi(): HomeUi {
return HomeUi()
}
#Provides
#Singleton
fun provideHomePresenter(homeUi: HomeUi): HomePresenter {
return HomePresenter(homeUi)
}
}
Those injected fields in HomeUi.kt
#Inject lateinit var context: Context
#Inject lateinit var presenter: HomePresenter
And this one in HomePresenter.kt
#Inject lateinit var context: Context
Here my Deps Component
#Singleton
#Component(modules = arrayOf(
NetworkModule::class,
HomeModule::class
))
interface Deps {
fun inject(homePresenter: HomePresenter)
fun inject(homeActivity: HomeActivity)
fun inject(homeUi: HomeUi)
}
I am using Dagger 2.10 but a StackOverflowError is thrown. I am looking for a way to avoid my circular dependency.
Note : This is my HomeUi which is infinitely instantiate.
It seems like you'd be calling field injection on HomeUi from within your presenters constructor, thus triggering an infinite loop since neither object can finish being constructed without the other (?). This looks like a really bad approach and you should try to move your dependencies into the objects constructors instead of creating half-finished objects.
Use field injection primarily for objects that you can't create yourself, e.g. with Android framework types. IMHO inject(homeActivity: HomeActivity) should be the only method of your component.
Cyclic dependencies are hard to manage and there is no perfect solution, but you can try things like switching to Provider<HomePresenter> to delay the dependency and be able to resolve it this way.
The following should do what you intended, and please note how I'm using constructor injection instead of having 2 additional methods in the module.
#Singleton
#Component(modules = arrayOf(
NetworkModule::class,
HomeModule::class
))
interface Deps {
fun inject(homeActivity: HomeActivity)
}
#Module
class HomeModule(private val context: Context) {
#Provides
fun provideContext() = context
}
#Singleton
class HomeUi #Inject constructor(presenter : Provider<HomePresenter>, context : Context)
{
// use with presenter.get()
}
#Singleton
class HomePresenter #Inject constructor(homeUi : HomeUi)
Please note that using a Provider<T> is the cheapest way to resolve a cyclic dependency that I know of, but it might not be suited for every situation.
I never used Dagger 2 with kotlin but I use it in java. I usually create my Module with my view as param and my method provideHomeUi() return my view as parameter. With this you shouldnt have a StackOverflowError.
Btw, why are you using Dagger 2 with Kotlin and not a directly library for DI on Kotlin, such as Kodein, Koin etc...
Good luck.

Categories

Resources