Search code examples
androiddependency-injectionlocal-storagerepository-patternkoin

Koin Dependency Injection switching between local and remote data source


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.


Solution

  • 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()
            }
        }
    }