In this blog, We’ll learn coroutines background processing by creating a small project, that simply displays an image to the UI.
Objective
- Get Image from URL – So first we’re going to get an image from URL and obviously we can not do that on the main thread. So we need a background thread to do that. And we’re going to this with Coroutines.
- Apply Processing – Next stuff is after downloading the image. We’re going to use another dispatcher to process that image. So here we’re going to apply a filter to that image. So that we transform it in a certain way. We’re going to convert it to black and white. We will do that in a separate coroutine for background processing.
- Show it on ImageView – Finally, we’re going to show it on ImageView.
So it quite a simple idea, But the way we do it nicely. Because we are going to use coroutine for background processing. So while we are working live project we can understand how we can perform background processing with the bits of help of Coroutines.
Requirements
Importantly I want to mention here that there are some requirements for this example.
- Android Studio – First of all you have to AndroidStudio installed in your machine.
- Android Knowledge – You would have some Android development knowledge.
All right just go ahead and jump into the Android Studio.
Let’s create a new project with basic template. So first of all, I’m going to create a basic interface and you not have to worry about it, you can download the source code from bottom of this blog.
Add permission
Obviously, first thing we are going to download an image from server so we have to require Internet permission. So let open the android manifest add it.
<uses-permission android:name="android.permission.INTERNET" />
I’m going to use this URL here, So this is what we will downloading and this is what we will processing.
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <ImageView android:id="@+id/imageView" android:layout_width="500dp" android:layout_height="500dp" android:scaleType="fitCenter" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" android:visibility="gone" app:srcCompat="@mipmap/ic_launcher" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
Alright moving on we have a layout that has basically two elements. It has ProgressBar that will just show that something is going on in IO dispatcher. and it has an ImageView that is currently hidden. So this will become visible when we actually have something to display. and then we’ll hide the progressbar.
MainActivity
Create Filter
package com.backgroundprocessingexample import android.graphics.Bitmap import android.graphics.Color object Filter { fun apply(source: Bitmap): Bitmap { val width = source.width val height = source.height val pixels = IntArray(width * height) // get pixel array from source source.getPixels(pixels, 0, width, 0, 0, width, height) var R: Int var G: Int var B: Int var index: Int var threshHold: Int for (y in 0 until height) { for (x in 0 until width) { // get current index in 2D-matrix index = y * width + x // get color R = Color.red(pixels[index]) G = Color.green(pixels[index]) B = Color.blue(pixels[index]) val grey = (R + G + B) / 3 pixels[index] = Color.rgb(grey, grey, grey) } } // output bitmap val bitmapOut = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565) bitmapOut.setPixels(pixels, 0, width, 0, 0, width, height) return bitmapOut } }
one things that I added here it is the Filter. Now this filter has a function fun apply(source: Bitmap) on a bitmap and it’s simply converted to a bitmap and It’s simply converted to a bitmap out to a another image.
What this filter does, It converts images to black & white. It does handle some processing because it goes pixel by pixel to convert it into black & white. That will take some processing power. So that a background job, So we can apply our coroutines to it. We can’t do it on the main thread or It should not do this in main thread.
// Add Coroutines Dependency implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
So here in the dependency first of all go ahead and copy the above dependencies. After adding these dependencies let go ahead and sync the project.
Now let’s open the MainActivity, So first i want to load image here from URL to local bitmap. then we want to put that bitmap in our ImageView. So I’m creating a kotlin function.
private fun getImageFromUrl(): Bitmap = URL(IMAGE_URL).openStream().use { BitmapFactory.decodeStream(it) } private fun loadImage(bmp: Bitmap) { progressBar.visibility = View.GONE imageView.setImageBitmap(bmp) imageView.visibility = View.VISIBLE }
So this function simply returns a bitmap of whatever is returned from decode stream. Now Important thing is this is a network call. So we can not do that in main thread. So what we’ll like to do is we would like to open up a coroutines scope and do that call inside the scope on the IO dispatcher.
coroutineScope.launch { val remoteImageDeferred = coroutineScope.async(Dispatchers.IO) { getImageFromUrl() } val imageBitmap = remoteImageDeferred.await() //loadImage(imageBitmap) launch(Dispatchers.Default) { val filterBitmap = Filter.apply(imageBitmap) withContext(Dispatchers.Main) { loadImage(filterBitmap) } // Log.i(MainActivity.TAG, "Default. Thread: ${Thread.currentThread().name}") } }
So let create a coroutines scope, here I’m going to open the main scope. Because we have to update UI from IO. Otherwise, we can’t update UI from another thread, It has to from main thread.
So the final output is
package com.backgroundprocessingexample import android.graphics.Bitmap import android.graphics.BitmapFactory import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_main.* import kotlinx.coroutines.* import java.net.URL class MainActivity : AppCompatActivity() { private val IMAGE_URL = "https://androidwave.com/wp-content/uploads/2018/12/useful-tools-for-logging-debugging-in-android.png.webp" private val coroutineScope = CoroutineScope(Dispatchers.Main) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) coroutineScope.launch { val remoteImageDeferred = coroutineScope.async(Dispatchers.IO) { getImageFromUrl() } val imageBitmap = remoteImageDeferred.await() //loadImage(imageBitmap) launch(Dispatchers.Default) { val filterBitmap = Filter.apply(imageBitmap) withContext(Dispatchers.Main) { loadImage(filterBitmap) } // Log.i(MainActivity.TAG, "Default. Thread: ${Thread.currentThread().name}") } } } private fun getImageFromUrl(): Bitmap = URL(IMAGE_URL).openStream().use { BitmapFactory.decodeStream(it) } private fun loadImage(bmp: Bitmap) { progressBar.visibility = View.GONE imageView.setImageBitmap(bmp) imageView.visibility = View.VISIBLE } }
Conclusion
That all, this way you can perform background jobs using coroutines in Kotlin. Keep in touch for more coroutines tutorials
What is recommendation
If you are a beginner I would like to recommend please read basic concept of coroutines.
- Coroutines for beginners
- Coroutine Scope in Kotlin
- Suspending functions in Coroutine
- Dispatchers in Kotlin Coroutines
- Exception handling in coroutines