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.
Some of the Android Studio related instructions might be irrelevant depending on the version installed. Ask a question, if something doesn’t make sense!
Create a new empty project, and configure it, as shown below:
Create a layout that will resemble a slot machine with two buttons (start and stop):
<?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>
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"
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
}
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:
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
As suggested by the parameters of the `randomInt()`` function from Step 4 we need to decide on:
TextViews
that will display the values.Add the following attributes to MainActivity
:
private var coroutineJob: Job? = null
private val delay: Long=1_000
private val repetitions = 10
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 + "-"
}
}
}
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()
}
After making sure the app is working, update it to use viewbinding.
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?