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():

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:

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.