I am new to Android development and currently delving into app architecture, specifically the separation of concerns across different layers (domain, data, and app). My understanding is that the domain layer should be independent of the Android framework and exist as a pure Kotlin library. Consequently, I should not use Hilt within this layer but opt for Dagger instead.
In my setup, the domain layer defines a repository interface and a use case interface along with its implementation. The data layer is responsible for implementing the repository, and the app layer manages the activities and the Hilt application setup. My goal is to leverage Dagger for dependency injection in the domain layer, while using Hilt in both the app and data layers.
However, I've encountered a compilation error when attempting to inject a use case into an activity.
The error is as follows:
app/build/generated/hilt/component_sources/debug/application/MyApp_HiltComponents.java:137: error: [Dagger/MissingBinding] application.domain.usecases.MyUseCase cannot be provided without an @Provides-annotated method.
public abstract static class SingletonC implements FragmentGetContextFix.FragmentGetContextFixEntryPoint,
^
Missing binding usage:
application.domain.usecases.MyUseCase is injected at
application.ui.MainActivity.myUseCase
application.ui.MainActivity is injected at
application.ui.MainActivity_GeneratedInjector.injectMainActivity(application.ui.MainActivity) [application.MyApp_HiltComponents.SingletonC → application.MyApp_HiltComponents.ActivityRetainedC → application.MyApp_HiltComponents.ActivityC]
And here is a simplified version of my code:
Domain layer (Dagger, no Hilt):
interface MyRepository {
suspend fun findAllUser(): Flow<List<User>>
}
interface MyUseCase {
suspend fun findAllUserRandom(): Flow<List<User>>
}
class MyUseCaseImpl @Inject constructor(
private val myRepository: MyRepository,
): MyUseCase {
override suspend fun findAllUserRandom(): Flow<List<User>> {
return myRepository.findAll().map { list ->
list.filter {
Random.nextInt() % 2 == 0
}
}
}
}
@Module
class DomainModule {
@Provides
@Singleton
fun provideMyUseCase(myRepository: MyRepository): MyUseCase {
return MyUseCaseImpl(myRepository)
}
}
Data layer (with hilt):
class MyRepositoryImpl @Inject constructor(
private val myRoomDao: MyRoomDao,
): MyRepository {
override suspend fun findAllUser(): Flow<List<User>> {
return myRoomDao.findAll()
}
}
@Module
@InstallIn(SingletonComponent::class)
interface DataModule {
companion object {
@Singleton
@Provides
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"my_database"
).build()
}
@Singleton
@Provides
fun provideMyRoomDao(appDatabase: AppDatabase): MyRoomDao {
return appDatabase.myRoomDao()
}
}
@Binds
@Singleton
fun bindMyRepository(myRepositoryImpl: MyRepositoryImpl): MyRepository
}
App layer:
@HiltAndroidApp
class MyApp : Application()
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var myUseCase: MyUseCase // compile error
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Text("Hello World !")
}
}
}
}
}
Initially, I attempted to resolve this issue by including the DomainModule in the app layer's module, as shown below. However, I doubt this is an optimal solution:
@InstallIn(SingletonComponent::class)
@Module(
includes = [
DomainModule::class
]
)
interface DataModule
I'm seeking guidance on whether it is feasible to use Dagger exclusively in the domain layer and Hilt in the remaining layers (app and data). If so, how can I correctly implement this architecture?
Thank you for your assistance!