So I'm trying to figure out how to create a @Provides function for a class that have @Assisted inject, is it possible or even a good practice?
I'm trying to inject a Pager from the android Paging library 3 into my Repository, I already managed to do it when the PagingSource don't have dynamic(runtime) arguments. Here is my code:
@Qualifier
annotation class ComicPager
private const val PAGE_SIZE = 10
@Module
@InstallIn(SingletonComponent::class)
internal object PagerModule {
@ComicPager
@Provides
fun provideComicPager(
config: PagingConfig,
pagingSource: ComicPagingSource
): Pager<Int, ComicDto> =
Pager(config = config, pagingSourceFactory = { pagingSource })
@Provides
fun providePagingConfig(): PagingConfig = PagingConfig(pageSize = PAGE_SIZE)
}
And this is my ComicPagingSource
constructor:
internal class ComicPagingSource @Inject constructor(
private val comicDataSource: ComicDataSource
) : PagingSource<Int, ComicDto>() {
// Paging logic here...
}
And the ComicRepository where the Pager is injected:
@Singleton
internal class ComicRepository @Inject constructor(
private val comicDataSource: ComicDataSource,
private val pager: Pager<Int, ComicDto> // <- Pager injected!
) {
fun getComics(): Flow<PagingData<Comic>> {
return pager.flow
.map { pagingData ->
pagingData.map { comic -> comic.toComic() }
}
}
}
So far so good the Pager
is provided to the ComicRepository
.
Now I want to do the same thing for another PagingSource that I have that receives a id
as a parameter in the constructor, I thought that would be simple to just pass the parameter to the provider method but I can't find a way to do it. This is the constructor for the other PagingSource:
internal class CreatorPagingSource @AssistedInject constructor(
@Assisted private val creatorId: Int,
private val creatorDataSource: CreatorDataSource
) : PagingSource<Int, ComicDto>() {
// Paging logic here...
}
@AssistedFactory
internal interface Factory {
fun create(creatorId: Int): CreatorPagingSource
}
internal class CreatorPagingSourceFactoryProvider @Inject constructor(
private val creatorDataSource: CreatorDataSource
) :
CreatorPagingSource.Factory {
override fun create(creatorId: Int): CreatorPagingSource =
CreatorPagingSource(creatorId, creatorDataSource = creatorDataSource)
}
This is what I accomplished until now on my repository:
internal class CreatorRepository @Inject constructor(
private val pagingConfig: PagingConfig,
private val pagingSourceFactoryProvider: PagingSourceFactoryProvider,
) {
fun getCreatorComics(creatorId: Int): Flow<PagingData<Comic>> {
return Pager(
config = pagingConfig,
pagingSourceFactory = { pagingSourceFactoryProvider.create(creatorId) } // <- Unable to inject the Pager because can't create a Provider to this injector
).flow
.map { pagingData ->
pagingData.map { comic -> comic.toComic() }
}
}
}
But it is not to my liking because I'm still creating the Pager inside the getCreatorComics methods, can someone help me figure out how to pass an @Assisted parameter to a @Provides or point me to a better direction?
Usually, you inject the factory because you will only be able to create the object at run time. You can delegate the creation itself, but someone has to create it.
If the problem is because you create concrete implementations and therefore couple implementations I can provide an example of how you can avoid this. I'm not sure it's necessarily better but let me explain the reasoning as I go.
First, I've noticed you've implemented
CreatorPagingSource.Factory
but since this is an@AssisteFactory
dagger will create the implementation for you. So I'd remove the implementation.I often prefer to depend on interfaces rather than concrete classes, so for these cases I often do the following:
This is an interface for a
PagingSource
factory. You can now implement it using@AssistedFactory
like so:Notice how we extend the factory and override the
create
method changing it's return type. This is required byAssistedInject
- to return the concrete implementation and not an abstraction. This is legal in Java - you can change the return type as long as it's within the bounds of the method it overrides.You can now inject only
PagingSourceFactory
where you need it by writing a provider:And the repo can become:
The difference is that you're now creating the
PagingSource<Int, ComicDto>
abstraction and your repository doesn't need to know aboutCreatorPagingSource
. In the future you can change the dagger modules to return other implementations. I think it's even better like this for testing and injecting mock factories that create mock objects.Not sure if this will help your issue but I think it could be useful.