ZOFTINO.COM android and web dev tutorials

Android Kotlin ListView Example

It is very common in most of the applications that a list of data items is displayed in UI. In android applications, list of data items can be shown to users using ListView.

Data items in various types of applications which are displayed as list in UI are list of categories or products in shopping app, list of search results in apps with search functionality, list of articles in blog apps, list of tourist attractions in tourism related apps, list of movies, list of audio, etc.

Android ListView displays data vertically with each item positioned below the previous data items. ListView is scrollable. You can use ListView without any issues for displaying few data items in UI. But for displaying large volumes of data as list, you need to use RecyclerView to build well performing apps.

Table of Contents

ListView Adapter

ListView uses adapter object to display data. ListView adapters are implementations of ListAdapter interface. ListView adapters take list of data items and item layout to be used for each item as input. Once adapter object is crated, it needs to be passed to ListView object by calling setAdapter() method.

Android provides ArrayAdapter and CursorAdapter. ArrayAdapter can be used to display array of items in ListView. CursorAdapter is used for displaying data from Cursor in ListView.

Custom ListAdapter can be created by extending BaseAdapter or its subclasses and implementing getView and other methods. We will see how to create custom listview adapter in the following sections.

Steps to Use ListView

First you need to add ListView to layout of the activity or fragment which is responsible for displaying the list screen.

<ListView
    android:id="@+id/products"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

In the activity, obtain the ListView object.

val lv = findViewById<ListView>(R.id.products)

Then get list of data from local or remote data base and pass it to list view adapter instance. For examples list of strings can be shown in ListView using ArrayAdapter. Item layout simple_list_item_1 contains TextView which displays string item.

val prodAdapter = ArrayAdapter<String>(this, 
		android.R.layout.simple_list_item_1, prodsList)

Then pass the adapter object to the ListView by calling setAdapter method.

lv.adapter = prodAdapter

Custom ListAdapter

To display complex data items meaning data list containing items with each item having several properties, custom list adapter needs to be created. You can create custom ListAdapter by extending ArrayAdapter or BaseAdapter.

In the getView method, using the item position argument passed to it, you need to get data item for that position, then inflate item layout and populate the views in item layout with the data from the data item.

Following Kotlin code shows how to create custom ListAdapter.

import android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import java.util.*

class AttractionsAdapter(items: ArrayList<Attraction>, ctx: Context) :
        ArrayAdapter<Attraction>(ctx, R.layout.attraction_item, items) {

    //view holder is used to prevent findViewById calls
    private class AttractionItemViewHolder {
        internal var image: ImageView? = null
        internal var title: TextView? = null
        internal var description: TextView? = null
        internal var hours: TextView? = null
    }

    override fun getView(i: Int, view: View?, viewGroup: ViewGroup): View {
        var view = view

        val viewHolder: AttractionItemViewHolder

        if (view == null) {
            val inflater = LayoutInflater.from(context)
            view = inflater.inflate(R.layout.attraction_item, viewGroup, false)

            viewHolder = AttractionItemViewHolder()
            viewHolder.title = view!!.findViewById<View>(R.id.title) as TextView
            viewHolder.description = view.findViewById<View>(R.id.description) as TextView
            viewHolder.hours = view.findViewById<View>(R.id.hours) as TextView
            //shows how to apply styles to views of item for specific items
            if (i == 3)
                viewHolder.hours!!.setTextColor(Color.DKGRAY)
            viewHolder.image = view.findViewById<View>(R.id.image) as ImageView
        } else {
            //no need to call findViewById, can use existing ones from saved view holder
            viewHolder = view.tag as AttractionItemViewHolder
        }

        val attraction = getItem(i)
        viewHolder.title!!.text = attraction!!.title
        viewHolder.description!!.text = attraction.description
        viewHolder.hours!!.text = attraction.hours
        viewHolder.image!!.setImageResource(attraction.image)

        //shows how to handle events of views of items
        viewHolder.image!!.setOnClickListener {
            Toast.makeText(context, "Clicked image of " + attraction!!.title,
                    Toast.LENGTH_SHORT).show()
        }

        view.tag = viewHolder

        return view
    }
}

View Holder

The custom adapter shown above uses view holder object to avoid findViewById calls if view object exists for the position passed to getView method.

View holder object contains all the views of an item. View holder object is saved as tag in the parent view of item layout when it is created first time. In the getView method, if the view passed to it is not null, you can get the view holder object from the view and access the views of item.

Styling ListView

By styling the views of item layout used for displaying data items, you can make the look and feel of the ListView to match your apps theme. To differentiate each item in the list in terms of style, you can use different color, font, text size, etc for items in different positions and apply them in the listadapter to views of the items.

For example, you can assign a different text color to TextView of an item at a particular position in the list in the getView method of listadapter.

if(i == 3)
viewHolder.hours!!.setTextColor(Color.DKGRAY)

You can configure item divider height by setting the height on ListView object.

lv.dividerHeight = 10

ListView Handling Events

If your app needs to respond when an item in the list is clicked to show details of the item clicked or take some action, you need to handle item click events by adding OnItemClickListener to list view as shown below.

        lv.onItemClickListener = AdapterView.OnItemClickListener {
            adapterView, view, i, l ->
            Toast.makeText(this@PlaceAttractionsActivity,
                    "you selected item " + (i + 1),
                    Toast.LENGTH_LONG).show()
        }

If you need to track and respond to the events of views in items, you need to handle those events in the list adapter by adding appropriate event handler to the views of items. For example, following code adds OnClickListener to image view.

viewHolder.image!!.setOnClickListener {
    Toast.makeText(context, "Clicked image of " + attraction!!.title,
            Toast.LENGTH_SHORT).show()
}

ListView Kotlin Example

The ListView Kotlin Example displays a list of tourist attractions of a selected place.

android kotlin listview example

Main Activity

import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.view.View

class SelectPlaceActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.select_place)
    }

    fun showAttractions(view: View) {
        val i = Intent()
        i.setClass(this, PlaceAttractionsActivity::class.java)
        startActivity(i)
    }
}

List Activity

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.AdapterView
import android.widget.ListView
import android.widget.Toast
import java.util.*

class PlaceAttractionsActivity : AppCompatActivity() {

    private val attractionsByCity = HashMap<String, ArrayList<Attraction>>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.place_attractions)

        loadAttractionsData()

        val lv = findViewById<ListView>(R.id.place_attractions)

        val seattleAttractions = getAttractionsByPlace("Seattle")
        val attractionsAdapter = AttractionsAdapter(seattleAttractions, this)
        lv.adapter = attractionsAdapter

        lv.dividerHeight = 10

        lv.onItemClickListener = AdapterView.OnItemClickListener {
            adapterView, view, i, l ->
            Toast.makeText(this@PlaceAttractionsActivity,
                    "you selected attraction " + (i + 1),
                    Toast.LENGTH_LONG).show()
        }
    }

    fun getAttractionsByPlace(place: String): ArrayList<Attraction> {
        val attractions = attractionsByCity[place]
        if (attractions != null) return attractions else return ArrayList<Attraction>()
    }

    fun loadAttractionsData() {
        addSeattleAttractions(attractionsByCity)
    }

    fun addSeattleAttractions(attractionsByCity: MutableMap<String, ArrayList<Attraction>>) {
        val attractionsLst = ArrayList<Attraction>()

        var attraction = Attraction()
        attraction.title = "Seattle Space Needle"
        attraction.description = "Travel to the top of the Space Needle's 520 ft. " +
                "observation deck for unparalleled views of downtown Seattle, " +
                "Mt. Rainier, and the Puget Sound,"
        attraction.hours = "10AM - 8PM"
        attraction.image = R.drawable.space_needle

        attractionsLst.add(attraction)

        attraction = Attraction()
        attraction.title = "Pike Place Market"
        attraction.description = "Pike Place Market is a public market, " +
                "opened August 17, 1907, and one of the oldest continuously " +
                "operated public farmers' markets in the United States."
        attraction.hours = "9AM - 4PM"
        attraction.image = R.drawable.pike_place_market

        attractionsLst.add(attraction)

        attraction = Attraction()
        attraction.title = "Kerry Park"
        attraction.description = "Kerry Park is a 1.26-acre park on the " +
                "south slope of Queen Anne Hill in Seattle, Washington, " +
                "located at the corner of Second Avenue West and West Highland Drive"
        attraction.hours = "4AM - 11PM"
        attraction.image = R.drawable.kerry_park

        attractionsLst.add(attraction)

        attraction = Attraction()
        attraction.title = "Mt. Rainier National Park"
        attraction.description = "Mount Rainier is the highest mountain of the Cascade" +
                " Range of the Pacific Northwest, and the highest mountain" +
                " in the U.S. state of Washington."
        attraction.hours = "10AM - 5PM"
        attraction.image = R.drawable.mountrainier

        attractionsLst.add(attraction)

        attraction = Attraction()
        attraction.title = "The Museum of Flight"
        attraction.description = "The Museum of Flight is a private non-profit air " +
                "and space museum in the northwest United States. " +
                "It is located just south of Seattle."
        attraction.hours = "10AM - 5PM"
        attraction.image = R.drawable.musium_of_flights

        attractionsLst.add(attraction)

        attraction = Attraction()
        attraction.title = "Snoqualmie Falls"
        attraction.description = "Snoqualmie Falls is one of Washington state's" +
                " most popular scenic attractions. More than 1.5 million" +
                " visitors come to the Falls every year."
        attraction.hours = "4AM - 11PM"
        attraction.image = R.drawable.snoqualmie_falls

        attractionsLst.add(attraction)

        attractionsByCity.put("Seattle", attractionsLst)
    }
}

List Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_marginTop="8dp"
    tools:context=".PlaceAttractionsActivity">
    <ListView
        android:id="@+id/place_attractions"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

Item Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/image"
        android:layout_width="120dp"
        android:layout_height="wrap_content"
        android:src="@drawable/kerry_park"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/description"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16dp"
        android:textStyle="bold"
        app:layout_constraintLeft_toRightOf="@+id/image"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/description"
        android:layout_width="240dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="4dp"
        android:layout_marginTop="4dp"
        app:layout_constraintLeft_toRightOf="@+id/image"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/title" />

    <TextView
        android:id="@+id/hours_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hours: "
        android:textStyle="bold"
        app:layout_constraintLeft_toRightOf="@+id/image"
        app:layout_constraintRight_toLeftOf="@+id/hours"
        app:layout_constraintTop_toBottomOf="@+id/description" />

    <TextView
        android:id="@+id/hours"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        app:layout_constraintLeft_toRightOf="@+id/image"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/description" />

</android.support.constraint.ConstraintLayout>

Data Object

class Attraction {
    var title: String? = null
    var description: String? = null
    var hours: String? = null
    var image: Int = 0
}