Step #7: Adapting EditFragment

Fixing EditFragment is more involved.

Partly, that is because we use more functions from SingleModelMotor, such as in the fragment’s save() and delete() functions. Change those to get the ToDoModel by getting the value from the StateFlow:

  private fun save() {
    binding?.apply {
      val model = motor.states.value.item
      val edited = model?.copy(
        description = desc.text.toString(),
        isCompleted = isCompleted.isChecked,
        notes = notes.text.toString()
      ) ?: ToDoModel(
        description = desc.text.toString(),
        isCompleted = isCompleted.isChecked,
        notes = notes.text.toString()
      )

      edited.let { motor.save(it) }
    }

    navToDisplay()
  }

  private fun delete() {
    val model = motor.states.value.item

    model?.let { motor.delete(it) }
    navToList()
  }

value on StateFlow is whatever the last-emitted object was. In our case, it is the last-emitted view-state. So, here, we get the last-emitted view-state, get the model from it (if there was a model), and proceed as we did before.

In terms of populating the widgets, you might think that we would use the same approach that we did in DisplayFragment: collect() the StateFlow and use the model (if we have one) for the widget contents. Indeed, we do that… but there is a problem.

We are only updating our model when the user clicks the save app bar item. In particular, the user can edit the description or notes, or check the is-completed CheckBox, and then undergo a configuration change. Those edits are not reflected in our model, because we have not yet updated it — the user did not click the save app bar item. However, we really should try to hold onto the user’s edits, as the user may get irritated if we lose them just because they rotated the screen.

The good news is that Android automatically knows how to handle those edits. That savedInstanceState Bundle that we see in functions like onViewCreated() contains the edits, put there by Android as part of processing the configuration change. Even better is that Android automatically updates the widgets with those edits in the new fragment after the configuration change.

We just need to not screw it up.

Specifically, we need to make sure that if we have a saved instance state, it gets used to populate our widgets. Getting the data from the model is to be used if we do not have the state.

So, with all that in mind, replace onViewCreated() in EditFragment with this implementation:

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    viewLifecycleOwner.lifecycleScope.launchWhenStarted {
      motor.states.collect { state ->
        if (savedInstanceState == null) {
          state.item?.let {
            binding?.apply {
              isCompleted.isChecked = it.isCompleted
              desc.setText(it.description)
              notes.setText(it.notes)
            }
          }
        }
      }
    }
  }

We once again collect() our StateFlow, and we once again use the view-state to update the widgets… but only if our savedInstanceState is null. Otherwise, we assume that our widgets already have what we want from a pre-configuration change instance of our fragment.

At this point, the app should compile and run. More importantly, courtesy of the changes that we made in the past few tutorials, any to-do items that you enter will be saved and will be available in future runs of the app.


Prev Table of Contents Next

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