Step #8: Fixing the Repository

Now, we need to have the ToDoRepository really use the ToDoEntity.Store, rather than just get it in a constructor parameter.

However, we have problems. ToDoRepository works with models. ToDoEntity.Store works with entities. We are going to need to be able to convert between these two types.

To that end, add this constructor and function to ToDoEntity:

  constructor(model: ToDoModel) : this(
    id = model.id,
    description = model.description,
    isCompleted = model.isCompleted,
    notes = model.notes,
    createdOn = model.createdOn
  )

  fun toModel(): ToDoModel {
    return ToDoModel(
      id = id,
      description = description,
      isCompleted = isCompleted,
      notes = notes,
      createdOn = createdOn
    )
  }

These offer bi-directional conversion between a ToDoModel and a ToDoEntity. If we needed more data conversion between things that Room knows how to store and how we wanted to represent them in the models, we could have that logic here as well.

Then, replace the contents of ToDoRepository with the following:

package com.commonsware.todo.repo

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext

class ToDoRepository(
  private val store: ToDoEntity.Store,
  private val appScope: CoroutineScope
) {
  fun items(): Flow<List<ToDoModel>> =
    store.all().map { all -> all.map { it.toModel() } }

  fun find(id: String?): Flow<ToDoModel?> = store.find(id).map { it?.toModel() }

  suspend fun save(model: ToDoModel) {
    withContext(appScope.coroutineContext) {
      store.save(ToDoEntity(model))
    }
  }

  suspend fun delete(model: ToDoModel) {
    withContext(appScope.coroutineContext) {
      store.delete(ToDoEntity(model))
    }
  }
}

items() now calls all() on our ToDoEntity.Store, to retrieve all of the entities. We use the map() operator on Flow to convert the List of ToDoEntity into a corresponding List of ToDoModel. So, items() now returns a Flow for that list of models. Every time ToDoEntity.Store emits a new list of entities, our repository emits the corresponding list of models.

Similarly, find() now calls find() on the ToDoEntity.Store and uses map() to convert the entity into a model.

We also delegate our save() and delete() calls to their corresponding ones on ToDoEntity.Store. We use the constructor that we added to ToDoEntity to map from our models to our entities. And, we wrap the actual DAO calls in withContext(), using a CoroutineContext obtained from our CoroutineScope. This says “use this context (and job) to manage the work in this coroutine”. Since the scope and context are tied to that SupervisorJob, that job manages the work, rather than any job that was set up by callers of these suspend functions. We will see how this comes into play when we update our viewmodels, in the next tutorial.

So, right now, our app is broken. SingleModelMotor and RosterMotor are expecting the old, synchronous repository API, instead of this new coroutines-based one. We will fix that, and get our app working once again, in the next tutorial.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.