Coroutines Examples - Update UI
This is an android tutorial to help understand Coroutines using simple standalone examples.
What is a Coroutine?
It is a concurrency design pattern that you can use on Android to simplify code that executes asynchronously. Coroutines were added to Kotlin in version 1.3 and are based on established concepts from other languages.
On Android, coroutines help to manage long-running tasks that might otherwise block the main thread and cause your app to become unresponsive.
Example 1: Kotlin Coroutines - UI Update Example
This is a simple example showing how to use coroutines to perform background tasks while updating progress to the UI in parallel on main UI Thread.
Here is the demo screenshot of what is created:
Step 1: Create Project
Create a Kotlin Project in Android Studio. The project has to be a Kotlin one since we intend to use Coroutines.
Step 2: Add Dependencies
Then in your app/build.gradle
add the following dependencies:
implementation 'com.akexorcist:RoundCornerProgressBar:2.0.3'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.21"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.20"
The round corner progressbar is a third party library and is subsitutable.
Step 3: Design layout
The layout is our main activity layout. Add the 3 progressbars as well as a button:
activity_main.xml
<?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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:padding="16dp"
tools:context="com.elyeproj.democoroutinesrace.MainActivity">
<com.akexorcist.roundcornerprogressbar.RoundCornerProgressBar
android:id="@+id/progressBarRed"
android:tooltipText="Red"
android:layout_width="match_parent"
android:layout_height="50dp"
app:rcProgress="0.0"
app:rcMax="1000.0"
app:rcRadius="10dp"
app:rcBackgroundPadding="2dp"
app:rcProgressColor="#f00"/>
<com.akexorcist.roundcornerprogressbar.RoundCornerProgressBar
android:id="@+id/progressBarGreen"
android:tooltipText="Green"
android:layout_marginTop="16dp"
android:layout_width="match_parent"
android:layout_height="50dp"
app:rcProgress="0.0"
app:rcMax="1000.0"
app:rcRadius="10dp"
app:rcBackgroundPadding="2dp"
app:rcProgressColor="#0f0" />
<com.akexorcist.roundcornerprogressbar.RoundCornerProgressBar
android:id="@+id/progressBarBlue"
android:tooltipText="Blue"
android:layout_marginTop="16dp"
android:layout_width="match_parent"
android:layout_height="50dp"
app:rcProgress="0.0"
app:rcMax="1000.0"
app:rcRadius="10dp"
app:rcBackgroundPadding="2dp"
app:rcProgressColor="#00f" />
<Button
android:id="@+id/buttonStart"
android:layout_marginTop="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start" />
</LinearLayout>
Step 4: Implement a Continuation Interface
Then override the resume()
and resumeWithException
functions:
Here is the full class:
AndroidContinuation.kt
import android.os.Handler
import android.os.Looper
import kotlin.coroutines.experimental.AbstractCoroutineContextElement
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.ContinuationInterceptor
private class AndroidContinuation<in T>(val cont: Continuation<T>) : Continuation<T> by cont {
override fun resume(value: T) {
if (Looper.myLooper() == Looper.getMainLooper()) cont.resume(value)
else Handler(Looper.getMainLooper()).post { cont.resume(value) }
}
override fun resumeWithException(exception: Throwable) {
if (Looper.myLooper() == Looper.getMainLooper()) cont.resumeWithException(exception)
else Handler(Looper.getMainLooper()).post { cont.resumeWithException(exception) }
}
}
/**
* Android context, provides an AndroidContinuation, executes everything on the UI Thread
*/
object Android : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
AndroidContinuation(continuation)
}
Step 5: Do Work
Last but not least we come to our main activity.
As instance fields define a couple of variables, some to represent the Job tobe performed and a boolean to represent the completion statis of those jobs.
private var raceEnd = false
private var greenJob: Job? = null
private var redJob: Job? = null
private var blueJob: Job? = null
Here is the function to initiate the job:
private suspend fun startRunning(progressBar: RoundCornerProgressBar) {
progressBar.progress = 0f
while (progressBar.progress < 1000 && !raceEnd) {
delay(10)
progressBar.progress += (1..10).random()
}
if (!raceEnd) {
raceEnd = true
Toast.makeText(this, "${progressBar.tooltipText} won!", Toast.LENGTH_SHORT).show()
}
}
Here is the function to update the progressbars:
private fun startUpdate() {
resetRun()
greenJob = launch(Android) {
startRunning(progressBarGreen)
}
redJob = launch(Android) {
startRunning(progressBarRed)
}
blueJob =launch(Android) {
startRunning(progressBarBlue)
}
}
Here is the function to cancel the jobs:
Here is the full code:
MainActivity.kt
package com.elyeproj.democoroutinesrace
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.Toast
import com.akexorcist.roundcornerprogressbar.RoundCornerProgressBar
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch
import java.util.*
class MainActivity : AppCompatActivity() {
private var raceEnd = false
private var greenJob: Job? = null
private var redJob: Job? = null
private var blueJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
buttonStart.setOnClickListener {
startUpdate()
}
}
override fun onDestroy() {
super.onDestroy()
resetRun()
}
private fun startUpdate() {
resetRun()
greenJob = launch(Android) {
startRunning(progressBarGreen)
}
redJob = launch(Android) {
startRunning(progressBarRed)
}
blueJob =launch(Android) {
startRunning(progressBarBlue)
}
}
private suspend fun startRunning(progressBar: RoundCornerProgressBar) {
progressBar.progress = 0f
while (progressBar.progress < 1000 && !raceEnd) {
delay(10)
progressBar.progress += (1..10).random()
}
if (!raceEnd) {
raceEnd = true
Toast.makeText(this, "${progressBar.tooltipText} won!", Toast.LENGTH_SHORT).show()
}
}
fun ClosedRange<Int>.random() =
Random().nextInt(endInclusive - start) + start
private fun resetRun() {
raceEnd = false
greenJob?.cancel()
blueJob?.cancel()
redJob?.cancel()
}
}
Step 6: Run
Lastly run the project.
Reference
Download the code below:
Number | Link |
---|---|
1. | Download code |
2. | Follow code author |