12 min read

In this article, we will build a simple to-do list app that allows a user to add and display tasks.

In this process, we will learn the following:

  • How to build a user interface in Android Studio
  • Working with ListViews
  • How to work with Dialogs

This article is taken from the book Learning Kotlin by building Android Applications by Eunice Adutwumwaa Obugyei and Natarajan Raman. This book will teach you programming in Kotlin including data types, flow control, lambdas, object-oriented, and functional programming while building  Android Apps.

Creating the project

Let’s start by creating a new project in Android Studio, with the name TodoList. Select Add No Activity on the Add an Activity to Mobile screen:

When the project creation is complete, create a Kotlin Activity by selecting File | New | Kotlin Activity, as shown in the following screenshot:

This will start a New Android Activity wizardOn the Add an Activity to Mobile screen, select Basic Activity, as shown in the following screenshot:

Now, check Launcher Activity on the Customize the Activity screen, and click the Finish button:

Building your UI

In Android, the code for your user interface is written in XML. You can build your UI by doing either of the following:

  • Using the Android Studio Layout Editor
  • Writing the XML code by hand

Let’s go ahead and start designing our TodoList app.

Using the Android Studio layout editor

Android Studio provides a layout editor, which gives you the ability to build your layouts by dragging widgets into the visual editor. This will auto-generate the XML code for your UI.

Open the content_main.xml file.

Make sure the Design tab at the bottom of the screen is selected, as shown in the following screenshot:

To add a component to your layout, you just drag the item from the Palette on the left side of the screen. To find a component, either scroll through the items on the Palette, or click on the Palette search icon and search for the item you need.

If the Palette is not showing on your screen, select View | Tool Windows | Palette to display it.

Go ahead and add a ListView to your view. When a view is selected, its attributes are displayed in the XML Attributes editor on the right side of the screen. The Attributes editor allows you to view and edit the attributes of the selected component. Go ahead and make the following changes:

  • Set the ID as list_view
  • Change both the layout_width and layout_height attributes to match_parent
If the Attributes editor is not showing; select View | Tool Windows | Attributes to display it.

Now, select Text at the bottom of the editor window to view the generated XML code. You’ll notice that the XML code now has a ListView placed within the ConstraintLayout: 

A layout always has a root element. In the preceding code, ConstraintLayout is the root element.

Instead of using the layout editor, you could have written the previous code yourself. The choice between using the layout editor or writing the XML code is up to you. You can use the option that you’re most comfortable with. We’ll continue to make additions to the UI as we go along.

Now, build and run your code. as shown in the following screenshot:

As you can see, the app currently doesn’t have much to it. Let’s go ahead and add a little more flesh to it.

Since we’ll use the FloatingActionButton as the button the user uses to add a new item to their to-do list, we need to change its icon to one that makes its purpose quite clear.

Open the activity_main.xml file:

One of the attributes of the android.support.design.widget.FloatingActionButton is app:srcCompat. This is used to specify the icon for the FloatingActionButton. Change its value from @android:drawable/ic_dialog_email to @android:drawable/ic_input_add.

Build and run again. The FloatingActionButton at the bottom now looks like an Add icon, as shown in the following screenshot:

Adding functionality to the user interface

At the moment, when the user clicks on the Add button, a ticker shows at the bottom of the screen. This is because of the piece of code in the onCreate() method that defines and sets an OnClickListener to the FloatingActionButton:

fab.setOnClickListener { view ->
    Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
            .setAction("Action", null).show()
}

This is not ideal for our to-do list app. Let’s go ahead and create a new method in the MainActivity class that will handle the click event:

fun showNewTaskUI() {
}

The method currently does nothing. We’ll add code to show the appropriate UI soon. Now, replace the code within the setOnClickListener() call with a call to the new method:

fab.setOnClickListener { showNewTaskUI() }

Adding a new task

For adding a new task, we’ll show the user an AlertDialog with an editable field.

Let’s start by building the UI for the dialog. Right-click the res/layout directory and select New | Layout resource file, as shown in the following screenshot:

On the New Resource File window, change the Root element to LinearLayout and set the File name as dialog_new_task. Click OK to create the layout,  as shown in the following screenshot:

Open the dialog_new_task layout and add an EditText view to the LinearLayout. The XML code in the layout should now look like this:

The inputType attribute is used to specify what kind of data the field can take. By specifying this attribute, the user is shown an appropriate keyboard. For example, if the inputType is set to number, the numbers keyboard is displayed:

Now, let’s go ahead and add a few string resources we’ll need for the next section. Open the res/values/strings.xml file and add the following lines of code to the resources tag:

Add New TaskSave
  • The add_new_task_dialog_title string will be used as the title of our dialog
  • The save string will be used as the text of a button on the dialog

The best way to use an AlertDialog is by encapsulating it in a DialogFragment. The DialogFragment takes away the burden of handling the dialog’s life cycle events. It also makes it easy for you to reuse the dialog in other activities.

Create a new Kotlin class with the name NewTaskDialogFragment, and replace the class definition with the following lines of code:

class NewTaskDialogFragment: DialogFragment() {  // 1
    
    // 2
    interface NewTaskDialogListener {
        fun onDialogPositiveClick(dialog: DialogFragment, task: String)
        fun onDialogNegativeClick(dialog: DialogFragment)
    }
var newTaskDialogListener: NewTaskDialogListener? = null // 3

// 4
companion object {
fun newInstance(title: Int): NewTaskDialogFragment {

val newTaskDialogFragment = NewTaskDialogFragment()
val args = Bundle()
args.putInt("dialog_title", title)
newTaskDialogFragment.arguments = args
return newTaskDialogFragment
}
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { // 5
val title = arguments.getInt("dialog_title")
val builder = AlertDialog.Builder(activity) 
builder.setTitle(title) 

val dialogView = 
activity.layoutInflater.inflate(R.layout.dialog_new_task, null) 
val task = dialogView.findViewById(R.id.task)


        
builder.setView(dialogView)
.setPositiveButton(R.string.save, { dialog, id ->
newTaskDialogListener?.onDialogPositiveClick(this, 
task.text.toString);
})
.setNegativeButton(android.R.string.cancel, { dialog, 
id ->
newTaskDialogListener?.onDialogNegativeClick(this)
})
return builder.create()
}

override fun onAttach(activity: Activity) { // 6
super.onAttach(activity)
try {
newTaskDialogListener = activity as NewTaskDialogListener 
} catch (e: ClassCastException) {
throw ClassCastException(activity.toString() + " must 
implement NewTaskDialogListener")
}

}
}

Let’s take a closer look at what this class does:

  1. The class extends the DialogFragment class.
  2. It declares an interface with the name NewTaskDialogListener, which declares two methods:
    • onDialogPositiveClick(dialog: DialogFragment, task: String)
    • onDialogNegativeClick(dialog: DialogFragment)
  3. It declares a variable of type NewTaskDialogListener.
  4. It defines a method, newInstance(), in a companion object. By doing this, the method can be accessed without having to create an instance of the NewTaskDialogFragment class. The newInstance() method does the following:
    • It takes an Int parameter named title
    • It creates an instance of the NewTaskDialogFragment and passes the title as part of its arguments
    • It returns the new instance of the NewTaskDialogFragment
  5. It overrides the onCreateDialog() method. This method does the following:
    • It attempts to retrieve the title argument passed
    • It instantiates an AlertDialog builder and assigns the retrieved title as the dialog’s title
    • It uses the LayoutInflater of the DialogFragment instance’s parent activity to inflate the layout we created
    • Then, it sets the inflated view as the dialog’s view
    • Sets two buttons to the dialog: Save and Cancel
    • When the Save button is clicked, the text in the EditText will be retrieved and passed to the newTaskDialogListener variable via the onDialogPositiveClick() method
  6. In the onAttach() method, we attempt to assign the Activity object passed to the newTaskDialogListener variable created earlier. For this to work, the Activity object should implement the NewTaskDialogListener interface.

Now, open the MainActivity class. Change the class declaration to include implementation of the NewTaskDialogListener. Your class declaration should now look like this:

class MainActivity : AppCompatActivity(), NewTaskDialogFragment.NewTaskDialogListener {

And, add implementations of the methods declared in the NewTaskDialogListener by adding the following methods to the MainActivity class:

    override fun onDialogPositiveClick(dialog: DialogFragment, task:String) {
    }

override fun onDialogNegativeClick(dialog: DialogFragment) {
}

In the showNewTaskUI() method, add the following lines of code:

val newFragment = NewTaskDialogFragment.newInstance(R.string.add_new_task_dialog_title)
newFragment.show(fragmentManager, "newtask")

In the preceding lines of code, the newInstance() method in NewTaskDialogFragment is called to generate an instance of the NewTaskDialogFragment class. The show() method of the DialogFragment is then called to display the dialog.

Build and run. Now, when you click the Add button, you should see a dialog on your screen,  as shown in the following screenshot:

As you may have noticed, nothing happens when you click the SAVE button. In the  onDialogPositiveClick() method, add the line of code shown here:

Snackbar.make(fab, "Task Added Successfully", Snackbar.LENGTH_LONG).setAction("Action", null).show()

As we may remember, this line of code displays a ticker at the bottom of the screen.

Now, when you click the SAVE button on the New Task dialog, a ticker shows at the bottom of the screen.

We’re currently not storing the task the user enters. Let’s create a collection variable to store any task the user adds. In the MainActivity class, add a new variable of type ArrayList, and instantiate it with an empty ArrayList:

private var todoListItems = ArrayList()

In the onDialogPositiveClick() method, place the following lines of code at the beginning of the method definition:

todoListItems.add(task)
listAdapter?.notifyDataSetChanged()

This will add the task variable passed to the todoListItems data, and call notifyDataSetChanged() on the listAdapter to update the ListView.

Saving the data is great, but our ListView is still empty. Let’s go ahead and rectify that.

Displaying data in the ListView

To make changes to a UI element in the XML layout, you need to use the findViewById() method to retrieve the instance of the element in the corresponding Activity of your layout. This is usually done in the onCreate() method of the Activity.

Open MainActivity.kt, and declare a new ListView instance variable at the top of the class:

private var listView: ListView? = null

Next, instantiate the ListView variable with its corresponding element in the layout. Do this by adding the following line of code at the end of the onCreate() method:

listView = findViewById(R.id.list_view)

To display data in a ListView, you need to create an Adapter, and give it the data to display and information on how to display that data. Depending on how you want the data displayed in your ListView, you can either use one of the existing Android Adapters, or create your own. For now, we’ll use one of the simplest Android Adapters, ArrayAdapter. The ArrayAdapter takes an array or list of items, a layout ID, and displays your data based on the layout passed to it.

In the MainActivity class, add a new variable, of type ArrayAdapter:

private var listAdapter: ArrayAdapter? = null

Add the method shown here to the class:

private fun populateListView() {
    listAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, todoListItems)
    listView?.adapter = listAdapter
}

In the preceding lines of code, we create a simple ArrayAdapter and assign it to the listView as its Adapter.

Now, add a call to the previous method in the onCreate() method:

populateListView()

Build and run. Now, when you click the Add button, you’ll see your entry show up on the ListView, as shown in the following screenshot:

In this article, we built a simple TodoList app that allows a user to add new tasks, and edit or delete an already added task. In the process, we learned to use ListViews and Dialogs.

Next, to learn about the different datastore options available and how to use them to make our app more usable, read our book, Learning Kotlin by building Android Applications.

Read Next

6 common challenges faced by Android App developers.

Google plans to let the AMP Project have an open governance model, soon!

Entry level phones to taste the Go edition of the Android 9.0 Pie version


Subscribe to the weekly Packt Hub newsletter. We'll send you the results of our AI Now Survey, featuring data and insights from across the tech landscape.

* indicates required