Class Tutorial 03 : Kotlin Coroutine

Overview

This tutorial shows you how to build a basic, single activity ‘slot machine’ app that asynchronously generates three random numbers using Kotlin coroutines and updates the UI.

NOTE

Some of the Android Studio related instructions might be irrelevant depending on the version installed. Ask a question, if something doesn’t make sense!


Step 1

Create a new empty project, and configure it, as shown below:

An example of using the lazy keyword in Kotlin
An example of using the lazy keyword in Kotlin

Step 2

Create a layout that will resemble a slot machine with two buttons (start and stop):

An example of using the lazy keyword in Kotlin

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   tools:context=".MainActivity">

   <TextView
       android:text="left"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:id="@+id/left" app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintEnd_toStartOf="@+id/middle"
       app:layout_constraintBottom_toTopOf="@+id/start"
       android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
   <TextView
       android:text="middle"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:id="@+id/middle"
       app:layout_constraintStart_toEndOf="@+id/left" app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintEnd_toStartOf="@+id/right"
       app:layout_constraintBottom_toTopOf="@+id/start"
       app:layout_constraintVertical_bias="0.0" android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
   <TextView
       android:text="right"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:id="@+id/right"
       app:layout_constraintStart_toEndOf="@+id/middle" app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintBottom_toTopOf="@+id/stop"
       app:layout_constraintVertical_bias="0.0" android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
   <Button
       android:text="start"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:id="@+id/start" app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/left"
       app:layout_constraintEnd_toStartOf="@+id/stop"
       app:layout_constraintBottom_toBottomOf="parent"/>
   <Button
       android:text="stop"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:id="@+id/stop"
       app:layout_constraintStart_toEndOf="@+id/start"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/right"
       app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Step 3

Add the necessary dependencies in the app gradle to enable Kotlin Coroutines:

//coroutine
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"

Step 4

Inside MainActivity create a suspend function (will be called from a background thread) that will generate random ints between 0 10 with a delay and update a TextView from the UI thread:

private val TAG = "slotmachine"

//return a random number between 0 to 10
suspend fun randomInt(repetitions: Int, delay: Long, v: TextView): Int {
   var rand = -1
   for(i: Int in 0..repetitions){
      
       delay(delay)
      
       rand = (0..10).random()

       Log.d(TAG, "Background -> "+Thread.currentThread().name)

       //updating the UI from the main thread
       withContext(Dispatchers.Main){

           Log.d(TAG, "UI -> "+Thread.currentThread().name)
           v.text = rand.toString()
       }

   }
   return rand
}

Step 5

In Android Studio, keywords marked in red indicate unresolved references. This often means that a library hasn’t been imported. If you see class names in Red, mouse over the name, and you should see a resolve option in the menu that pops up:

An example of using the lazy keyword in Kotlin

Alternatively, you can add the import directives manually. In the case of this step, if you encounter issues with the imports, include the following:

import android.util.Log
import android.widget.TextView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext

Step 6

As suggested by the parameters of the `randomInt()`` function from Step 4 we need to decide on:

  1. the number of repetitions (how many times the slow machine will spin)
  2. the delay between the repetitions
  3. The TextViews that will display the values.
  4. Run the method asynchronously and control the process via a Job.

Add the following attributes to MainActivity:

private var coroutineJob: Job? = null
private val delay: Long=1_000
private val repetitions = 10

Step 7

Create a function that will run three randomInt() functions concurrently and update the three TextViews with the final values. Read the comments carefully :

fun slotMachineDraw(left: TextView, middle: TextView, right: TextView)  {
   //cancel the job object in case user wants to start it again
   coroutineJob?.cancel()
   // .launch returns a Job object that we can then use to cancel the coroutine
   coroutineJob = CoroutineScope(Dispatchers.IO).launch {

       // 'async' will let us run the randInt generators concurrently.
       // notice the parameters that we are passing to randomInt()
       val leftDeferred = async {
           randomInt(repetitions,delay, left)
       }

       val middleDeferred = async {
           randomInt(repetitions,delay, middle)
       }

       val rightDeferred = async {
           randomInt(repetitions,delay, right)
       }

       // 'await' is needed to retrieve the final result once it is available
       val left_final = leftDeferred.await()
       val middle_final = middleDeferred.await()
       val right_final = rightDeferred.await()
       // we can display the final values with hyphens on both ends
       withContext(Dispatchers.Main) {

           Log.d(TAG, "Final values UI -->"+Thread.currentThread().name)

           left.text = "-" + left_final + "-"
           middle.text = "-" + middle_final + "-"
           right.text = "-" + right_final + "-"
       }
   }
  }

Step 8

Activate the start and stop buttons by adding the following to onCreate:

//start
(findViewById<Button>(R.id.start)).setOnClickListener{
   slotMachineDraw((findViewById<TextView>(R.id.left)),
       (findViewById<TextView>(R.id.middle)),
       (findViewById<TextView>(R.id.right)))
}

//stop
(findViewById<Button>(R.id.stop)).setOnClickListener{
   coroutineJob?.cancel()
}

Step 9

After making sure the app is working, update it to use viewbinding.

Step 10

Challenge! Run the app and test it. Try quitting it shortly after pressing ‘start’ and looking at the logcat. Why are the threads still running? How can you fix this?