Step #3: Responding to List Clicks
The typical way lists work in Android is that if you tap on a row in the list, the user is taken to some UI (activity or fragment) that pertains to the tapped-upon row. More generally, we have ways of finding out when the row gets clicked upon. Once we get control when the user taps on a row, we need to show that DisplayFragment
… even if at the moment it will be empty.
At this point, we have two separate events to track: clicking on the row and toggling the checked state of the CheckBox
. We can set up separate callback functions for those.
To that end, modify the constructor for RosterRowHolder
to have both onCheckboxToggle
and onRowClick
parameters:
class RosterRowHolder(
private val binding: TodoRowBinding,
val onCheckboxToggle: (ToDoModel) -> Unit,
val onRowClick: (ToDoModel) -> Unit
) :
Then, update bind()
to add a new line to the apply()
call:
fun bind(model: ToDoModel) {
binding.apply {
root.setOnClickListener { onRowClick(model) }
isCompleted.isChecked = model.isCompleted
isCompleted.setOnCheckedChangeListener { _, _ -> onCheckboxToggle(model) }
desc.text = model.description
}
Click events that are not handled by child widgets ripple up the view hierarchy. So, by putting a click listener on the ConstraintLayout
— the root
of our binding — we will find out if the user clicks on the TextView
or any empty space in the row. And, here, we invoke our onRowClick
function type. At this point, we are getting control for both UI events and routing them to the appropriate function types.
At this point, though, RosterAdapter
will have a compile error, as we are not passing in both onRowClick
and onCheckboxToggle
. So, modify its constructor to accept both of those:
class RosterAdapter(
private val inflater: LayoutInflater,
private val onCheckboxToggle: (ToDoModel) -> Unit,
private val onRowClick: (ToDoModel) -> Unit
) :
…and modify onCreateViewHolder()
to pass both along to the RosterRowHolder
constructor:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
RosterRowHolder(
TodoRowBinding.inflate(inflater, parent, false),
onCheckboxToggle,
onRowClick
)
Now, of course, RosterListFragment
has a compile error, as we are not providing both onRowClick
and onCheckboxToggle
. We need to provide the onRowClick
value and do something to show the DisplayFragment
.
With that in mind, add the following function to RosterListFragment
:
private fun display(model: ToDoModel) {
findNavController().navigate(RosterListFragmentDirections.displayModel())
}
Our primary gateway to the Navigation component from our Kotlin code is by findNavController()
. We can call this on any activity or fragment and get a NavController
back that we can use to navigate through our navigation graph, among other things.
Here, we call navigate()
on the NavController
, to indicate that we want to transition from this destination to another one in our navigation graph. Because we added the Safe Args plugin back in the preceding tutorial, our parameter to navigate()
is a “directions” object. We get ours by calling displayModel()
on RosterListFragmentDirections
. The ...Directions
class will have a name based on the destination that we are coming from — we are in the rosterListFragment
destination in our navigation graph, so our class is RosterListFragmentDirections
. The function that we call is based on the ID of the action that we want to perform. We gave our action an ID of displayModel
earlier in this tutorial, so our function is displayModel()
. We pass whatever that function returns to navigate()
, and the Navigation component will arrange to perform that action and send us to whatever destination it designates.
In the IDE, you will see that our model
parameter to our display()
function is unused. This hints at a flaw in our implementation. The idea is that we want DisplayFragment
to display the details of some to-do item. That implies that DisplayFragment
knows what that to-do item actually is. We have the model
parameter, but we are not somehow getting that information over to the DisplayFragment
. We will address this shortcoming in a later step of this tutorial.
So, modify the RosterAdapter
constructor call in onViewCreated()
of RosterListFragment
to look like this:
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)
Here, to help us keep track of our parameters, we are taking advantage of Kotlin’s named arguments. We have our original lambda expression assigned to onCheckboxToggle
, and we have a function reference to our display()
function assigned to onRowClick
.
If you run the sample app now, and you click on one of the to-do items, you will be taken to the DisplayFragment
… which happens to not display anything. We will fix that in the upcoming steps of this tutorial.
If you press BACK when viewing the (empty) DisplayFragment
, you will return to the list of to-do items.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.