I have a multi-module project where I want to separate data and domain logics from each other in two different modules. (At the moment they are all in core module) : https://github.com/alirezaeiii/TMDb-Compose-Playground
I have a logic for supporting pagination in different screen in my app :
private const val STARTING_PAGE_INDEX = 1
abstract class BasePagingSource<T : TMDbItem>(private val context: Context) : PagingSource<Int, T>() {
protected abstract suspend fun fetchItems(page: Int): List<T>
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
val page = params.key ?: STARTING_PAGE_INDEX
return try {
val response = fetchItems(page)
LoadResult.Page(
data = response,
prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1,
nextKey = if (response.isEmpty()) null else page + 1
)
} catch (exception: IOException) {
LoadResult.Error(TMDbException(context.getString(R.string.failed_loading_msg)))
} catch (exception: HttpException) {
LoadResult.Error(TMDbException(context.getString(R.string.failed_loading_msg)))
}
}
override fun getRefreshKey(state: PagingState<Int, T>): Int? {
return state.anchorPosition?.let {
state.closestPageToPosition(it)?.prevKey?.plus(1)
?: state.closestPageToPosition(it)?.nextKey?.minus(1)
}
}
}
And I have a base repository class :
abstract class BasePagingRepository<T : TMDbItem> {
protected abstract fun pagingSource(query: String?): BasePagingSource<T>
fun fetchResultStream(query: String?= null): Flow<PagingData<T>> = Pager(
config = PagingConfig(pageSize = NETWORK_PAGE_SIZE),
pagingSourceFactory = { pagingSource(query) }
).flow
companion object {
private const val NETWORK_PAGE_SIZE = 20
}
}
The issue with this approach is all sub classes extend this as the same :
@Singleton
class AiringTodayTvSeriesPagingRepository @Inject constructor(
@ApplicationContext private val context: Context,
private val tvShowApi: TVShowService
) : BasePagingRepository<TVShow>() {
override fun pagingSource(query: String?): BasePagingSource<TVShow> =
AiringTodayTvSeriesPagingSource(context, tvShowApi)
}
Or :
@Singleton
class OnTheAirTvSeriesPagingRepository @Inject constructor(
@ApplicationContext private val context: Context,
private val tvShowApi: TVShowService
) : BasePagingRepository<TVShow>() {
override fun pagingSource(query: String?): BasePagingSource<TVShow> =
OnTheAirTvSeriesPagingSource(context, tvShowApi)
}
and etc in com.sample.tmdb.core.data.repository package in core module. So they all for instance extend BasePagingRepository<TVShow>
. Of-course I have used dependency injection for repositories :
@Singleton
@Binds
internal abstract fun bindTrendingTVShowRepository(trendingTvSeriesPagingRepository: TrendingTvSeriesPagingRepository): BasePagingRepository<TVShow>
@Singleton
@Binds
internal abstract fun bindPopularTVShowRepository(popularTvSeriesPagingRepository: PopularTvSeriesPagingRepository): BasePagingRepository<TVShow>
@Singleton
@Binds
internal abstract fun bindAiringTodayTVShowRepository(airingTodayTvSeriesPagingRepository: AiringTodayTvSeriesPagingRepository): BasePagingRepository<TVShow>
@Singleton
@Binds
internal abstract fun bindOnTheAirTVShowRepository(onTheAirTvSeriesPagingRepository: OnTheAirTvSeriesPagingRepository): BasePagingRepository<TVShow>
So when I want to inject these repositories in my ViewModels, I have to use concrete implementation rather than abstraction or interface (Dependency inversion
in Solid) such as :
@HiltViewModel
class AiringTodayTvSeriesViewModel @Inject constructor(repository: AiringTodayTvSeriesPagingRepository) :
BaseMainPagingViewModel<TVShow>(repository)
So 1st of all I did not follow Dependency inversion and 2nd I have to add both data
and domain
module dependency to my feature modules. Which I think would not be good and it should be enough to just add domain
dependency as my interface and abstract repositories are there.
How do you approach to solve this issue? I appreciate for any suggestion.
I concluded to use qualifier annotations for this purpose while I am using Hilt, such as :
In the di module, I have used it as follow :
And simply I used the annotation in the ViewModel :