Step #5: Updating the Local Items
Now we need to integrate ToDoRemoteDataSource
into the rest of the app. ToDoRepository
should be the one to do that, as a repository is supposed to insulate the GUI code from this sort of external interaction.
So, ToDoRepository
needs an instance of ToDoRemoteDataSource
. We could have ToDoRepository
create its own instance, but it is better to use Koin — that way, we can use a mock ToDoRemoteDataSource
in testing.
So, in ToDoApp
, add these two lines to the existing koinModule
declaration:
single { OkHttpClient.Builder().build() }
single { ToDoRemoteDataSource(get()) }
The first line creates a singleton instance of OkHttpClient
(using an OkHttpClient.Builder
), while the second line creates a singleton instance of ToDoRemoteDataSource
.
Next, add a ToDoRemoteDataSource
to the ToDoRepository
constructor:
class ToDoRepository(
private val store: ToDoEntity.Store,
private val appScope: CoroutineScope,
private val remoteDataSource: ToDoRemoteDataSource
) {
That then requires us to update its corresponding code in the Koin configuration in ToDoApp
, adding a second get()
call to pull in the ToDoRemoteDataSource
:
single {
ToDoRepository(
get<ToDoDatabase>().todoStore(),
get(named("appScope")),
get()
)
}
The end goal is that we want to get these new to-do items into our database, if those items are not there already. More importantly, if they are already in our database, we want to leave the database alone, as we may have local changes to the items that we do not want to overwrite. However, our save()
function in ToDoEntity.Store
is set up to replace existing items:
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(vararg entities: ToDoEntity)
That is great for user edits, but it is not what we want for our server-defined items. So, add this importItems()
function to ToDoEntity.Store
:
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun importItems(entities: List<ToDoEntity>)
This has two differences when compared with save()
:
- It uses
OnConflictStrategy.IGNORE
to say “if this item already exists based on the primary key, skip the insert operation for that item” - It uses a
List
of entities rather thanvarargs
The function is named importItems()
, rather than just import()
, because import
is a keyword in Java/Kotlin. While we can still have an import()
function, we cannot have fields or properties named import
. To avoid this sort of collision, we are using importItems
as the name of this function instead of import
.
To use importItems()
, we need a way to map ToDoServerItem
objects to ToDoEntity
objects. So, add this toEntity()
function to ToDoServerItem
:
fun toEntity(): ToDoEntity {
return ToDoEntity(
id = id,
description = description,
isCompleted = completed,
notes = notes,
createdOn = createdOn
)
}
This just does a property-level mapping of the ToDoServerItem
to the corresponding ToDoEntity
definition.
Now, we can glue all of this together. Add this importItems()
function to ToDoRepository
:
suspend fun importItems(url: String) {
withContext(appScope.coroutineContext) {
store.importItems(remoteDataSource.load(url).map { it.toEntity() })
}
}
Here, we:
- Take a URL to our JSON as a parameter
- Use the
ToDoRemoteDataSource
to get the list ofToDoServerItem
objects - Use
map()
andtoEntity()
to convert that list into a list ofToDoEntity
objects - Use
importItems()
onToDoEntity.Store
to insert any new items into our database, skipping existing ones
Since both load()
and importItems()
(on ToDoEntity.Store
) are suspend
functions, we use suspend
on importItems()
in ToDoRepository
. And, we use our appScope
so the import will proceed even if the user exits our UI while that import is going on.
So, now our repository knows how to get items and import them into our database.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.