So I am writing this app with a remote data source. And I wanted to add local db storage capabilities. I have setup an architecture whereby I have an interface DataSource
. A RemoteDataSource
and a LocalDataSource
classes implement that interface. The RemoteDataSource
is injected with ApiInterface
retrofit and the LocalDataSource
is injected with DAOs.
Now there is this repository interface and implementation SomeDataRepository
and SomeDataRepositoryImpl
. If I want the repository to be able to fetch the data from API and save it to the database, how do I go about doing that?
I have injected both the RemoteDataSource
and LocalDataSource
classes to the SomeDataRepositoryImpl
to access methods from different data sources. This way I can call something like localDataSource.saveToDb()
and/or remoteDatSource.fetchSomeData()
int SomeRepositoryImpl
class. But I do not know if passing concrete implementations to a class is the way to go.
But if I pass lets say a single DataSource
interface to the SomeDataRepository
, I will have to define a saveToDb()
function in the interface DataSource
and then I will have to implement that in RemoteDataSource
as well which is not that good.
Can anyone please guide me through what the best approach is to this solution?
And also while I am at it, is it any good to wrap the data with the LiveData
wrapper class right in the API interface for retrofit? because I don't think when a method is called on the repository, I would want to observe it right there in the repo and then access the data to put it onto local db.
Since you want to have the local data source act as a fallback for the remote data source, you can create another data source implementation that is a composition of the local and remote data sources. This composite data source can contain the fallback logic and handle the delegation to the remote and local datasources as needed. Once you have done this, it is a simple matter to create a Koin module to construct these, and bind the composite data source to the data source interface.
Suppose this is your interface and the two data sources you already have:
interface DataSource {
fun getData(): Data
}
class RemoteDataSource : DataSource {
// ...
}
class LocalDataSource : DataSource {
// ...
}
Then you can create a third implementation like this one:
class CompositeDataSource(
val remote: RemoteDataSource,
val local: LocalDataSource
) : DataSource {
override fun getData() : Data {
return try {
remote.getData()
} catch (e: Exception) {
local.getData()
}
}
}
To define all of this, your koin module would look something like this
module {
single { RemoteDataSource() }
single { LocalDataSource() }
single<DataSource> { CompositeDataSource(remote = get(), local = get()) }
}
Edit: If what you actually want is a cache, you can use the local data source as your cache like this:
class CompositeDataSource(
val remote: RemoteDataSource,
val local: LocalDataSource
) : DataSource {
override fun getData() : Data {
return try {
remote.getData().also { local.saveData(it) }
} catch (e: Exception) {
local.getData()
}
}
}