遵循CLEAN架构,Android领域层(Dagger)与应用/数据层(Hilt)集成问题
我是Android开发新手,正在研究应用架构的关注点分离,尤其是领域层、数据层和应用层。我希望领域层独立于Android框架,作为纯Kotlin库存在,所以领域层只用Dagger,不引入Hilt;应用层和数据层则使用Hilt。
当前架构设置:
- 领域层:定义Repository接口、UseCase接口及其实现,用Dagger的Module提供依赖
- 数据层:实现Repository接口,用Hilt管理依赖
- 应用层:管理Activity和Hilt应用配置
尝试将UseCase注入Activity时遇到编译错误,错误信息如下:
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]
简化代码
领域层(仅Dagger,无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) } }
数据层(用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 }
应用层
@HiltAndroidApp class MyApp : Application() @AndroidEntryPoint class MainActivity : ComponentActivity() { @Inject lateinit var myUseCase: MyUseCase // 编译错误 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { Text("Hello World !") } } } } }
我尝试通过在应用层模块中引入DomainModule来解决问题,代码如下,但不确定这是否是最优方案:
@InstallIn(SingletonComponent::class) @Module( includes = [ DomainModule::class ] ) interface DataModule
我的疑问:是否可以在领域层仅使用Dagger,其余层(应用、数据)使用Hilt?如果可行,该如何正确实现此架构?
解决方案
可行性确认
完全可以实现领域层用纯Dagger、应用/数据层用Hilt的架构——因为Hilt本质是Dagger的Android扩展,二者可以无缝兼容。领域层保持纯Kotlin库的独立性,不依赖Android框架和Hilt,只需要用标准Dagger注解定义模块和依赖即可。
正确实现方式
你尝试的**在Hilt模块中引入Dagger的DomainModule**是完全合理且最优的方案之一,具体原因和优化点如下:
1. 核心原理
Hilt的组件(比如SingletonComponent)会自动包含所有通过@InstallIn指定的模块,以及这些模块includes的其他Dagger模块。把DomainModule通过includes加入到Hilt的模块中,就能让Hilt的组件识别到领域层提供的MyUseCase绑定,解决MissingBinding错误。
2. 优化实现
你当前的代码可以直接运行,但可以做一点小调整让结构更清晰:
- 领域层的
DomainModule保持原样(纯Dagger,无Hilt依赖) - 在应用层单独创建一个Hilt模块来引入领域层模块,而不是和数据层模块合并,这样职责更单一:
// 应用层的Hilt模块,专门负责引入领域层的Dagger模块 @InstallIn(SingletonComponent::class) @Module(includes = [DomainModule::class]) class DomainHiltBridgeModule
这样数据层的DataModule只负责数据相关的绑定,DomainHiltBridgeModule专门做领域层和Hilt的桥接,架构分层更清晰。
3. 其他注意事项
- 领域层的
@Singleton注解要和Hilt的SingletonComponent兼容:因为Hilt的SingletonComponent对应应用生命周期的单例,和Dagger的@Singleton语义一致,所以领域层用@Singleton没问题。 - 确保领域层的依赖都能被Hilt提供:比如
MyRepository由数据层的Hilt模块绑定,所以领域层的MyUseCase依赖能被正确注入。
为什么你的尝试有效
当你在DataModule中includes = [DomainModule::class]时,Hilt会把DomainModule中的provideMyUseCase方法加入到自己的组件绑定图中,这样在注入MainActivity的myUseCase时,Hilt就能找到对应的提供方法,解决编译错误。
内容的提问来源于stack exchange,提问作者emerick biron

