Final Results

The new actions_edit menu resource should contain:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:android="http://schemas.android.com/apk/res/android">

  <item
    android:id="@+id/save"
    android:icon="@drawable/ic_save"
    android:title="@string/menu_save"
    app:showAsAction="ifRoom|withText" />
</menu>

EditFragment should resemble:

package com.commonsware.todo

import android.os.Bundle
import android.view.*
import android.view.inputmethod.InputMethodManager
import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.commonsware.todo.databinding.TodoEditBinding
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf

class EditFragment : Fragment() {
  private var binding: TodoEditBinding? = null
  private val args: EditFragmentArgs by navArgs()
  private val motor: SingleModelMotor by viewModel { parametersOf(args.modelId) }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setHasOptionsMenu(true)
  }

  override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
  ) = TodoEditBinding.inflate(inflater, container, false)
    .apply { binding = this }
    .root

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    motor.getModel()?.let {
      binding?.apply {
        isCompleted.isChecked = it.isCompleted
        desc.setText(it.description)
        notes.setText(it.notes)
      }
    }
  }

  override fun onDestroyView() {
    binding = null

    super.onDestroyView()
  }

  override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
    inflater.inflate(R.menu.actions_edit, menu)

    super.onCreateOptionsMenu(menu, inflater)
  }

  override fun onOptionsItemSelected(item: MenuItem): Boolean {
    when (item.itemId) {
      R.id.save -> {
        save()
        return true
      }
    }

    return super.onOptionsItemSelected(item)
  }

  private fun save() {
    binding?.apply {
      val model = motor.getModel()
      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 navToDisplay() {
    hideKeyboard()
    findNavController().popBackStack()
  }

  private fun hideKeyboard() {
    view?.let {
      val imm = context?.getSystemService<InputMethodManager>()

      imm?.hideSoftInputFromWindow(
        it.windowToken,
        InputMethodManager.HIDE_NOT_ALWAYS
      )
    }
  }
}

RosterMotor should look like:

package com.commonsware.todo

import androidx.lifecycle.ViewModel

class RosterMotor(private val repo: ToDoRepository) : ViewModel() {
  fun getItems() = repo.items

  fun save(model: ToDoModel) {
    repo.save(model)
  }
}

And RosterListFragment should resemble:

package com.commonsware.todo

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.commonsware.todo.databinding.TodoRosterBinding
import org.koin.androidx.viewmodel.ext.android.viewModel
import androidx.navigation.fragment.findNavController

class RosterListFragment : Fragment() {
  private val motor: RosterMotor by viewModel()
  private var binding: TodoRosterBinding? = null

  override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
  ): View = TodoRosterBinding.inflate(inflater, container, false)
    .also { binding = it }
    .root

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    val adapter = RosterAdapter(
      layoutInflater,
      onCheckboxToggle = { motor.save(it.copy(isCompleted = !it.isCompleted)) },
      onRowClick = ::display)

    binding?.items?.apply {
      setAdapter(adapter)
      layoutManager = LinearLayoutManager(context)

      addItemDecoration(
        DividerItemDecoration(
          activity,
          DividerItemDecoration.VERTICAL
        )
      )
    }

    adapter.submitList(motor.getItems())
    binding?.empty?.visibility = View.GONE
  }

  override fun onDestroyView() {
    binding = null

    super.onDestroyView()
  }

  private fun display(model: ToDoModel) {
    findNavController()
      .navigate(RosterListFragmentDirections.displayModel(model.id))
  }
}

Prev Table of Contents Next

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