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.