Save remote data separately related tables when using NetworkBoundRepository with coroutines (android)

I want to use Single source of truth principle in my application. How can I add multiple table when using NetworkBoundRepository.

MainApi.kt

interface MainApi {
    @GET("main")
    suspend fun getMain(): Response<MainResponse>
}

MainResponse.kt

@JsonClass(generateAdapter = true)
data class MainResponse(
        @Json(name = "categories") val categoryList: List<Category>,
        @Json(name = "locations") val locationList: List<Location>,
        @Json(name = "tags") val tagList: List<Tag>
) 

NetworkBoundRepository.kt

@ExperimentalCoroutinesApi
abstract class NetworkBoundRepository<RESULT, REQUEST> {

    fun asFlow() = flow<Resource<RESULT>> {
        emit(Resource.Success(fetchFromLocal().first()))
        val apiResponse = fetchFromRemote()
        val remoteCategories = apiResponse.body()

        if (apiResponse.isSuccessful && remoteCategories != null) {
            saveRemoteData(remoteCategories)
        } else {
            emit(Resource.Failed(apiResponse.message()))
        }

        emitAll(
            fetchFromLocal().map {
                Resource.Success<RESULT>(it)
            }
        )
    }.catch { e ->
        emit(Resource.Failed("Network error! Can't get latest categories."))
    }

    @WorkerThread
    protected abstract suspend fun saveRemoteData(response: REQUEST)

    @MainThread
    protected abstract fun fetchFromLocal(): Flow<RESULT>

    @MainThread
    protected abstract suspend fun fetchFromRemote(): Response<REQUEST>
}

MainRepository.kt

@ExperimentalCoroutinesApi
class MainRepository @Inject constructor(
    private val mainApi: MainApi,
    private val categoryDao: CategoryDao,
    private val locationDao: LocationDao,
    private val tagDao: TagDao
) {
        suspend fun getMain(): Flow<Resource<List<Category>>> {
        return object : NetworkBoundRepository<List<Category>, List<Category>>() {
            override suspend fun saveRemoteData(response: List<Category>) = categoryDao.insertList(response)
            override fun fetchFromLocal(): Flow<List<Category>> = categoryDao.getList()
            override suspend fun fetchFromRemote(): Response<List<Category>> = mainApi.getMain()
        }.asFlow()
    }
}

Currently NetworkBoundRepository and MainRepository only works with categories. I want to fetch some data from internet and save each data to related tables in database. App must be offline first. How can I add locationDao, tagDao to MainRepository?

Answer

I don’t quite follow your question. You are adding locationDao and tagDao to MainRepository already here:

class MainRepository @Inject constructor(
    ...
    private val locationDao: LocationDao,
    private val tagDao: TagDao
)

If you are asking how to provide them in order for them to injectable via Dagger2 you have to either define dao constructor as @Inject or add @Provides or @Binds annotated methods with the relevant return type to the needed @Module, and tangle them in the same @Scope – more here

If you asking how to use those repos in your functions it is also easy:

object : NetworkBoundRepository<List<Category>, MainResponse>() {
            override suspend fun saveRemoteData(response: MainResponse) = response?.run{
                categoryDao.insertList(categoryList)
                locationDao.insertList(locationList)
                tagDao.insertList(tagList)
            }
            override fun fetchCategoriesFromLocal(): Flow<List<Category>> = categoryDao.getList()
            override fun fetchLocationsFromLocal(): Flow<List<Location>> = locationDao.getList()
            override fun fetchTagsFromLocal(): Flow<List<Tag>> = tagDao.getList()
            override suspend fun fetchFromRemote(): Response<MainResponse> = mainApi.getMain()

//This function is not tested and written more like a pseudocode
            override suspend fun mapFromLocalToResponse(): Flow<MainResponse> = fetchCategoriesFromLocal().combine(fetchLocationsFromLocal(), fetchTagsFromLocal()){categories, locations, tags ->
                MainResponse(categories,locations,tags)
            }
        }

Maybe some more adjustments will be needed. But the main problem of your code is that you are trying to combine all the different entities into one repo and it is not very good(and the request that returns all the stuff under one response is not good either) – I would suggest to split it somehow not to mix it all.

Leave a Reply

Your email address will not be published. Required fields are marked *