Chapters

Hide chapters

Android Apprentice

Fourth Edition · Android 11 · Kotlin 1.4 · Android Studio 4.1

Section II: Building a List App

Section 2: 7 chapters
Show chapters Hide chapters

Section III: Creating Map-Based Apps

Section 3: 7 chapters
Show chapters Hide chapters

10. Completing the Detail View
Written by Darryl Bayliss

Heads up... You’re accessing parts of this content for free, with some sections shown as kkqicvqag text.

Heads up... You’re accessing parts of this content for free, with some sections shown as xzgyxvzoq text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the last chapter, you set up a new Activity to display the contents of a list. At the moment, that Activity is empty.

In this chapter, you’ll add to that Activity using familiar components such as a RecyclerView to display the list, and a FloatingActionButton to add tasks to the list. You’ll also learn how to communicate back to the previous Activity using an Intent.

Getting started

If you’re following along with your own project, open it and keep using it with this chapter. If not, don’t worry. Locate the projects folder for this chapter and open the Listmaker app inside the starter folder.

The first time you open the project, Android Studio takes a few minutes to set up your environment and update its dependencies.

Open ListDetailActivity.kt and review its contents.

Currently, you pass in a list from MainActivity.kt via an Intent and set the title of the Activity to the name of the list. That’s good, but this Activity needs to do more. For starters, it needs to let a user view all of the items in the list, as well as add new items.

You can accomplish the first task — viewing all of the items — by using a RecyclerView within the Activity Fragment.

Open list_detail_fragment.xml from the res/layout folder, and show the Design view in the Layout window if it’s not already selected.

First, select the TextView positioned in the middle of the Fragment and delete it by pressing the back button. In the Palette window, select the Common option from the left-hand list. You’ll see the RecyclerView available for selection in the right-hand list.

Click and drag the RecyclerView to the whitespace in the Layout shown on the right of the Layout Window.

With the RecyclerView added, you need to give it an ID and some dimensions. In the Attributes window, change the ID of the RecyclerView to list_items_recyclerview.

Next, update the layout_width and layout_height to 0dp match_constraint. This ensures the RecyclerView adheres to the constraints you’re about to set, and that it takes up the entire screen.

In the Constraint Widget, click the four + buttons around the square to add constraints to the RecyclerView. Change the margins for each constraint to 0.

With the RecyclerView set up in the layout, it’s time to use it in your code.

Coding the RecyclerView

Open ListDetailFragment.kt. At the top of the class, add a property to hold a reference to the ViewBinding for the Fragment:

lateinit var binding: ListDetailFragmentBinding

Heads up... You’re accessing parts of this content for free, with some sections shown as ggnaqxsen text.

Heads up... You’re accessing parts of this content for free, with some sections shown as szlezclem text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {

  // 1
  binding = ListDetailFragmentBinding.inflate(inflater, container, false)

  // 2
  return binding.root
}  

Heads up... You’re accessing parts of this content for free, with some sections shown as pwnyzckes text.

Heads up... You’re accessing parts of this content for free, with some sections shown as htvujfgyb text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
class ListItemsRecyclerViewAdapter(var list: TaskList) : RecyclerView.Adapter<ListItemViewHolder>() {

class ListItemViewHolder(val binding: ListItemViewHolderBinding) : RecyclerView.ViewHolder(binding.root)

Setting up the Adapter

Open ListItemsRecyclerViewAdapter.kt.

Heads up... You’re accessing parts of this content for free, with some sections shown as jwsutkbum text.

Heads up... You’re accessing parts of this content for free, with some sections shown as nfhuwvxur text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Heads up... You’re accessing parts of this content for free, with some sections shown as gfnuvpgej text.

Heads up... You’re accessing parts of this content for free, with some sections shown as bggelspyl text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
override fun getItemCount(): Int {
  return list.tasks.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListItemViewHolder {

  val binding = ListItemViewHolderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
  return ListItemViewHolder(binding)
}

Heads up... You’re accessing parts of this content for free, with some sections shown as wdqywwpec text.

Heads up... You’re accessing parts of this content for free, with some sections shown as twnajvpic text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Heads up... You’re accessing parts of this content for free, with some sections shown as pdpisqtoz text.

Heads up... You’re accessing parts of this content for free, with some sections shown as fmsinfpyx text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Adding the ViewHolder

You now have a Layout for the ViewHolder, next, you need to hook up the data to the TextView using the ViewBinding.

override fun onBindViewHolder(holder: ListItemViewHolder, position: Int) {
  holder.binding.textViewTask.text = list.tasks[position]
}

Heads up... You’re accessing parts of this content for free, with some sections shown as dnxoxscyt text.

Heads up... You’re accessing parts of this content for free, with some sections shown as lmnihltig text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Heads up... You’re accessing parts of this content for free, with some sections shown as lxpuvtcut text.

Heads up... You’re accessing parts of this content for free, with some sections shown as lznyprlyw text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

lateinit var binding: ListDetailActivityBinding
binding = ListDetailActivityBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)

binding.addTaskButton.setOnClickListener {
  showCreateTaskDialog()
}
lateinit var viewModel: ListDetailViewModel

lateinit var fragment: ListDetailFragment
viewModel = ViewModelProvider(this).get(ListDetailViewModel::class.java)
viewModel.list = intent.getParcelableExtra(MainActivity.INTENT_LIST_KEY)!!
list = intent.getParcelableExtra(MainActivity.INTENT_LIST_KEY)!!
title = viewModel.list.name

Heads up... You’re accessing parts of this content for free, with some sections shown as ltcalgvan text.

Heads up... You’re accessing parts of this content for free, with some sections shown as vptabvdam text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Passing the Task to the RecyclerView

Still in ListDetailActivity below onCreate(), add a new method. The methods purpose is to show a dialog to the user, asking for the task to add to the list:

private fun showCreateTaskDialog() {
  //1
  val taskEditText = EditText(this)
  taskEditText.inputType = InputType.TYPE_CLASS_TEXT

  //2
  AlertDialog.Builder(this)
          .setTitle(R.string.task_to_add)
          .setView(taskEditText)
          .setPositiveButton(R.string.add_task) { dialog, _ ->
            // 3
            val task = taskEditText.text.toString()
            // 4
            viewModel.addTask(task)
            //5
            dialog.dismiss()
          }
          //6
          .create()
          .show()
}
<string name="task_to_add">What is the task you want to add?</string>
<string name="add_task">Add</string>
class ListDetailViewModel() : ViewModel() {

  lateinit var onTaskAdded: (() -> Unit)

  lateinit var list: TaskList

}
fun addTask(task: String) {
  list.tasks.add(task)
  onTaskAdded.invoke()
}
val recyclerAdapter = ListItemsRecyclerViewAdapter(viewModel.list)
binding.listItemsRecyclerview.adapter = recyclerAdapter
binding.listItemsRecyclerview.layoutManager = LinearLayoutManager(requireContext())

viewModel.onTaskAdded = {
  recyclerAdapter.notifyDataSetChanged()
}

Heads up... You’re accessing parts of this content for free, with some sections shown as qzcimlcak text.

Heads up... You’re accessing parts of this content for free, with some sections shown as xdkydctuw text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Returning Results from Activities

If you were to go back from the Detail Activity to the Main Activity, then open the Detail Activity again. Your newly added task would disappear! The reason for that is the scope of the ViewModel ends when the Activity disappears. Since the tasks aren’t being saved anywhere, they are lost.

private fun showListDetail(list: TaskList) {
  val listDetailIntent = Intent(this, ListDetailActivity::class.java)
  listDetailIntent.putExtra(INTENT_LIST_KEY, list)

  startActivityForResult(listDetailIntent, LIST_DETAIL_REQUEST_CODE)
}

Heads up... You’re accessing parts of this content for free, with some sections shown as xsxicwjyl text.

Heads up... You’re accessing parts of this content for free, with some sections shown as lnkahbgor text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

companion object {
  const val INTENT_LIST_KEY = "list"
  const val LIST_DETAIL_REQUEST_CODE = 123
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data:
    Intent?) {
  super.onActivityResult(requestCode, resultCode, data)
  // 1
  if (requestCode == LIST_DETAIL_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
    // 2
    data?.let {
      // 3
      viewModel.updateList(data.getParcelableExtra(INTENT_LIST_KEY)!!)
      viewModel.refreshLists()
    }
  }
}

Heads up... You’re accessing parts of this content for free, with some sections shown as ktvebnvox text.

Heads up... You’re accessing parts of this content for free, with some sections shown as msqalwjov text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
fun updateList(list: TaskList) {
  sharedPreferences.edit().putStringSet(list.name, list.tasks.toHashSet()).apply()
  lists.add(list)
}

fun refreshLists() {
  lists.clear()
  lists.addAll(retrieveLists())
}

Heads up... You’re accessing parts of this content for free, with some sections shown as nhvoxkzuj text.

Heads up... You’re accessing parts of this content for free, with some sections shown as fmrinwjuz text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
override fun onBackPressed() {
  val bundle = Bundle()
  bundle.putParcelable(MainActivity.INTENT_LIST_KEY, viewModel.list)

  val intent = Intent()
  intent.putExtras(bundle)
  setResult(Activity.RESULT_OK, intent)
  super.onBackPressed()
}

Key Points

This chapter has used a lot of what you’ve learned from the previous chapters. It also introduced you to new concepts such as:

Where to go from here?

In the next chapter, you’ll learn how to take your app and make it work on Android tablets, as well as on Android phones!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2025 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as jnvojpjuf text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now