Crime in CrimeDetailFragment doesn't survive process death: Intentional? or a bug?

If a crime is changed on CrimeDetailFragment and the app’s process is subsequently killed by the OS before navigating back to CrimeListFragment, that changed crime doesn’t get saved in the database because CrimeDetailViewModel.onCleared() doesn’t get called.

The issue is that CrimeDetailViewModel.updateCrime function doesn’t go as far as saving the changes made to the database, but instead only updates _crime stateflow in the viewmodel.

Also does anyone agree with me that this breaks the practice/principle of single source of truth of data? At any given time we cannot say for certain if the source of crime in CrimeDetailFragment is the database or an updated version in the viewmodel. Another way to look at it is that _crime in the viewmodel has two sources of its value; (1) the repository (ultimately the database) and (2) a direct mutation in updateCrime function.

The way I dealt with this issue is as follows:

  • Starting at CrimeDao, change the return type of getCrime(Int) to Flow<Crime>
    fun getCrime(id: UUID): Flow<Crime>
    
  • CrimeRepository doesn’t change.
  • In CrimeDetailViewModel observe this Flow<Crime> in the init block
    init {
      viewModelScope.launch {
          crimeRepository.getCrime(crimeId).collect {
              _crime.value = it
          }
      }
    }
    
  • Also in CrimeDetailViewModel change updateCrime function to
    fun updateCrime(onUpdate: (Crime) -> Crime) = _crime.value?.let { oldCrime ->
        val newCrime = onUpdate(oldCrime)
        crimeRepository.updateCrime(newCrime)
    }
    

If this turns out to be a bug - and I am not just missing something (which is very likely), I suggest the book leaves this bug in place and makes it an end-of-chapter challenge to deal with it. I certainly enjoyed it and was very proud of my self when I managed to make it work.

Aside:
Please note the above updateCrime function hasn’t been tested because I refactored my own code further such that the function simply becomes

fun updateCrime(crime: Crime) = crimeRepository.updateCrime(crime)

This refactoring was to deal with an issue mentioned on page 295

This might seem like a strange place to set a View.onClickListener, but …

I completely agree with the book as updateUi function should only update the UI. Is anyone interested in discussing this issue further in a separate topic?

Thank you for bringing this up. We talked about this a lot while writing the 5th edition. All the concerns that you brought up are totally valid and you found a great solution to get things working the way you want them. Ultimately, this is a case where the requirements for this feature were not super detailed, so there were a few cases where the expected behavior was not clearly defined. Saving changes manually vs. saving changes whenever the user popped off CrimeDetailFragment vs. saving changes as the user makes them all require slightly different solutions. You found a good solution for saving changes as the user makes them.

1 Like