Updating the Database

The database serves as the single source of truth for crime data. In CriminalIntent, when the user leaves the detail screen, any edits they made should be saved to the database. (Other apps might have other requirements, like having a “save” button or saving updates as the user types.)

First, add a function to your CrimeDao to update an existing crime. While you are at it, also add a function to insert a new crime. You will mostly ignore the insert function for now, but you will use it in Chapter 14 when you add a menu option to the UI to create new crimes.

Listing 12.13  Adding update and insert database functions (database/CrimeDao.kt)

@Dao
interface CrimeDao {

  @Query("SELECT * FROM crime")
  fun getCrimes(): LiveData<List<Crime>>

  @Query("SELECT * FROM crime WHERE id=(:id)")
  fun getCrime(id: UUID): LiveData<Crime?>

  @Update
  fun updateCrime(crime: Crime)

  @Insert
  fun addCrime(crime: Crime)
}

The annotations for these functions do not require any parameters. Room can generate the appropriate SQL command for these operations.

The updateCrime() function uses the @Update annotation. This function takes in a crime object, uses the ID stored in that crime to find the associated row, and then updates the data in that row based on the new data in the crime object.

The addCrime() function uses the @Insert annotation. The parameter is the crime you want to add to the database table.

Next, update the repository to call through to the new insert and update DAO functions. Recall that Room automatically executes the database queries for CrimeDao.getCrimes() and CrimeDao.getCrime(UUID) on a background thread because those DAO functions return LiveData. In those cases, LiveData handles ferrying the data over to your main thread so you can update your UI.

You cannot, however, rely on Room to automatically run your insert and update database interactions on a background thread for you. Instead, you must execute those DAO calls on a background thread explicitly. A common way to do this is to use an executor.

Using an executor

An Executor is an object that references a thread. An executor instance has a function called execute that accepts a block of code to run. The code you provide in the block will run on whatever thread the executor points to.

You are going to create an executor that uses a new thread (which will always be a background thread). Any code in the block will run on that thread, so you can perform your database operations there safely.

You cannot implement an executor in the CrimeDao directly, because Room generates the function implementations for you based on the interface you define. Instead, implement the executor in CrimeRepository. Add a property to the executor to hold a reference, then execute your insert and update functions using the executor.

Listing 12.14  Inserting and updating with an executor (CrimeRepository.kt)

class CrimeRepository private constructor(context: Context) {
  ...
  private val crimeDao = database.crimeDao()
  private val executor = Executors.newSingleThreadExecutor()

  fun getCrimes(): LiveData<List<Crime>> = crimeDao.getCrimes()

  fun getCrime(id: UUID): LiveData<Crime?> = crimeDao.getCrime(id)

  fun updateCrime(crime: Crime) {
      executor.execute {
          crimeDao.updateCrime(crime)
      }
  }

  fun addCrime(crime: Crime) {
      executor.execute {
          crimeDao.addCrime(crime)
      }
  }
  ...
}

The newSingleThreadExecutor() function returns an executor instance that points to a new thread. Any work you execute with the executor will therefore happen off the main thread.

Both updateCrime() and addCrime() wrap their calls to the DAO inside the execute {} block. This pushes these operations off the main thread so you do not block your UI.

Tying database writes to the fragment lifecycle

Last but not least, update your app to write the values the user enters in the crime detail screen to the database when the user navigates away from the screen.

Open CrimeDetailViewModel.kt and add a function to save a crime object to the database.

Listing 12.15  Adding save capability (CrimeDetailViewModel.kt)

class CrimeDetailViewModel() : ViewModel() {
    ...
    fun loadCrime(crimeId: UUID) {
        crimeIdLiveData.value = crimeId
    }

    fun saveCrime(crime: Crime) {
        crimeRepository.updateCrime(crime)
    }
}

saveCrime(Crime) accepts a Crime and writes it to the database. Since CrimeRepository handles running the update request on a background thread, the database integration here is very simple.

Now, update CrimeFragment to save the user’s edited crime data to the database.

Listing 12.16  Saving in onStop() (CrimeFragment.kt)

class CrimeFragment : Fragment() {
    ...
    override fun onStart() {
        ...
    }

    override fun onStop() {
        super.onStop()
        crimeDetailViewModel.saveCrime(crime)
    }

    private fun updateUI() {
        ...
    }
    ...
}

Fragment.onStop() is called any time your fragment moves to the stopped state (that is, any time the fragment moves entirely out of view). This means the data will get saved when the user finishes the detail screen (such as by pressing the Back button). The data will also be saved when the user switches tasks (such as by pressing the Home button or using the overview screen). Thus, saving in onStop() meets the requirement of saving the data whenever the user leaves the detail screen and also ensures that, if the process is killed due to memory pressure, the edited data is not lost.

Run CriminalIntent and select a crime from the list. Change data in that crime. Press the Back button and pat yourself on the back for your most recent success: The changes you made on the detail screen are now reflected in the list screen. In the next chapter, you will hook up the date button on the detail screen to allow the user to select the date when the crime occurred.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.98.93