Final Results
Our new actions_roster
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/add"
android:icon="@drawable/ic_add"
android:title="@string/menu_add"
app:showAsAction="ifRoom|withText" />
</menu>
The nav_graph
resource should resemble:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph.xml"
app:startDestination="@id/rosterListFragment">
<fragment
android:id="@+id/rosterListFragment"
android:name="com.commonsware.todo.RosterListFragment"
android:label="@string/app_name">
<action
android:id="@+id/displayModel"
app:destination="@id/displayFragment" />
<action
android:id="@+id/createModel"
app:destination="@id/editFragment" >
<argument
android:name="modelId"
android:defaultValue="@null" />
</action>
</fragment>
<fragment
android:id="@+id/displayFragment"
android:name="com.commonsware.todo.DisplayFragment"
android:label="@string/app_name" >
<argument
android:name="modelId"
app:argType="string" />
<action
android:id="@+id/editModel"
app:destination="@id/editFragment" />
</fragment>
<fragment
android:id="@+id/editFragment"
android:name="com.commonsware.todo.EditFragment"
android:label="@string/app_name" >
<argument
android:name="modelId"
app:argType="string"
app:nullable="true" />
</fragment>
</navigation>
SingleModelMotor
should look like:
package com.commonsware.todo
import androidx.lifecycle.ViewModel
class SingleModelMotor(
private val repo: ToDoRepository,
private val modelId: String?
) : ViewModel() {
fun getModel() = repo.find(modelId)
fun save(model: ToDoModel) {
repo.save(model)
}
fun delete(model: ToDoModel) {
repo.delete(model)
}
}
ToDoRepository
should resemble:
package com.commonsware.todo
class ToDoRepository {
var items = emptyList<ToDoModel>()
fun save(model: ToDoModel) {
items = if (items.any { it.id == model.id }) {
items.map { if (it.id == model.id) model else it }
} else {
items + model
}
}
fun find(modelId: String?) = items.find { it.id == modelId }
fun delete(model: ToDoModel) {
items = items.filter { it.id != model.id }
}
}
RosterListFragment
should look like:
package com.commonsware.todo
import android.os.Bundle
import android.view.*
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 onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
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 =
if (adapter.itemCount == 0) View.VISIBLE else View.GONE
}
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.actions_roster, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.add -> {
add()
return true
}
}
return super.onOptionsItemSelected(item)
}
private fun display(model: ToDoModel) {
findNavController()
.navigate(RosterListFragmentDirections.displayModel(model.id))
}
private fun add() {
findNavController().navigate(RosterListFragmentDirections.createModel(null))
}
}
The actions_edit
menu resource should now resemble:
<?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" />
<item
android:id="@+id/delete"
android:icon="@drawable/ic_delete"
android:title="@string/menu_delete"
app:showAsAction="ifRoom|withText" />
</menu>
SingleModelMotor
should look like:
package com.commonsware.todo
import androidx.lifecycle.ViewModel
class SingleModelMotor(
private val repo: ToDoRepository,
private val modelId: String?
) : ViewModel() {
fun getModel() = repo.find(modelId)
fun save(model: ToDoModel) {
repo.save(model)
}
fun delete(model: ToDoModel) {
repo.delete(model)
}
}
Finally, EditFragment
overall should look like:
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)
menu.findItem(R.id.delete).isVisible = args.modelId != null
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.save -> {
save()
return true
}
R.id.delete -> {
delete()
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 delete() {
val model = motor.getModel()
model?.let { motor.delete(it) }
navToList()
}
private fun navToDisplay() {
hideKeyboard()
findNavController().popBackStack()
}
private fun navToList() {
hideKeyboard()
findNavController().popBackStack(R.id.rosterListFragment, false)
}
private fun hideKeyboard() {
view?.let {
val imm = context?.getSystemService<InputMethodManager>()
imm?.hideSoftInputFromWindow(
it.windowToken,
InputMethodManager.HIDE_NOT_ALWAYS
)
}
}
}
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.