简体   繁体   中英

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM