Separate data and domain layers in a multi-module project and follow D in Solid

91 views Asked by At

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.

1

There are 1 answers

0
Ali On BEST ANSWER

I concluded to use qualifier annotations for this purpose while I am using Hilt, such as :

@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class Discover

In the di module, I have used it as follow :

    @Singleton
    @Discover
    @Binds
    internal abstract fun bindDiscoverMoviesRepository(discoverMoviesPagingRepository: DiscoverMoviesPagingRepository): BasePagingRepository<Movie>

And simply I used the annotation in the ViewModel :

@HiltViewModel
class DiscoverMoviesViewModel @Inject constructor(
    @Discover repository: BasePagingRepository<Movie>
) : BaseMainPagingViewModel<Movie>(repository)