Skip to content

Coil Examples - Coroutine Image Loader

A step by step Coil tutorial and examples.

What is Coil?

Image loading for Android backed by Kotlin Coroutines..

An image loading library for Android backed by Kotlin Coroutines. Coil is:

  • Fast: Coil performs a number of optimizations including memory and disk caching, downsampling the image in memory, automatically pausing/cancelling requests, and more.
  • Lightweight: Coil adds ~2000 methods to your APK (for apps that already use OkHttp and Coroutines), which is comparable to Picasso and significantly less than Glide and Fresco.
  • Easy to use: Coil's API leverages Kotlin's language features for simplicity and minimal boilerplate.
  • Modern: Coil is Kotlin-first and uses modern libraries including Coroutines, OkHttp, Okio, and AndroidX Lifecycles.

Coil is an acronym for: Coroutine Image Loader.

To use coil in your android app follow these steps:

Step 1: Install it

Coil is available on mavenCentral(). Install it via the following command:

implementation("io.coil-kt:coil:2.2.2")

Step 2: Write code

ImageViews

To load an image into an ImageView, use the load extension function:

// URL
imageView.load("https://www.example.com/image.jpg")

// File
imageView.load(File("/path/to/image.jpg"))

// And more...

Requests can be configured with an optional trailing lambda:

imageView.load("https://www.example.com/image.jpg") {
    crossfade(true)
    placeholder(R.drawable.image)
    transformations(CircleCropTransformation())
}

Jetpack Compose

Import the Jetpack Compose extension library:

implementation("io.coil-kt:coil-compose:2.2.2")

To load an image, use the AsyncImage composable:

AsyncImage(
    model = "https://example.com/image.jpg",
    contentDescription = null
)

Image Loaders

Both imageView.load and AsyncImage use the singleton ImageLoader to execute image requests. The singleton ImageLoader can be accessed using a Context extension function:

val imageLoader = context.imageLoader

ImageLoaders are designed to be shareable and are most efficient when you create a single instance and share it throughout your app. That said, you can also create your own ImageLoader instance(s):

val imageLoader = ImageLoader(context)

If you do not want the singleton ImageLoader, depend on `io.coil-kt:coil-baseinstead ofio.coil-kt:coil`.

Requests

To load an image into a custom target, enqueue an ImageRequest:

val request = ImageRequest.Builder(context)
    .data("https://www.example.com/image.jpg")
    .target { drawable ->
        // Handle the result.
    }
    .build()
val disposable = imageLoader.enqueue(request)

To load an image imperatively, execute an ImageRequest:

val request = ImageRequest.Builder(context)
    .data("https://www.example.com/image.jpg")
    .build()
val drawable = imageLoader.execute(request).drawable

Check out Coil's full documentation here.

Requirements

Coil requires the following:

  • Min SDK 21+
  • Java 8+

R8 / Proguard

Coil is fully compatible with R8 out of the box and doesn't require adding any extra rules. If you use Proguard, you may need to add rules for Coroutines, OkHttp and Okio.

Full Example

For a full Coil example project follow the following steps.

Step 1. Our Android Manifest

We will need to look at our AndroidManifest.xml.

(a). AndroidManifest.xml

Our AndroidManifest file.

Here we will add the following permission:

  1. Our ACCESS_NETWORK_STATE permission.
  2. Our INTERNET permission.

Here is the full Android Manifest file:

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:name=".Application"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:ignore="GoogleAppIndexingWarning">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

Step 2. Design Layouts

In Android we design our UI interfaces using XML. So let's create the following layouts:

(a). list_item.xml

Our list_item layout.

Inside your /res/layout/ directory create an xml layout file named list_item.xml.

After that design your XML layout using the following 1 UI widgets and ViewGroups:

  1. ImageView - Where we will load our image
<?xml version="1.0" encoding="utf-8"?>
<ImageView
    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:clickable="true"
    android:contentDescription="@null"
    android:focusable="true"
    android:foreground="?attr/selectableItemBackground"
    android:scaleType="centerCrop"
    tools:ignore="UnusedAttribute"
    tools:viewBindingIgnore="true"/>

(b). activity_main.xml

Our activity_main layout.

Inside your /res/layout/ directory create an xml layout file named activity_main.xml.

Furthermore design your XML layout using the following 5 UI widgets and ViewGroups:

  1. androidx.constraintlayout.widget.ConstraintLayout - Our root element
  2. com.google.android.material.appbar.AppBarLayout - Our AppBar layout
  3. com.google.android.material.appbar.MaterialToolbar - Our toolbar
  4. androidx.recyclerview.widget.RecyclerView - A recyclerView
  5. ImageView
<?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">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="?attr/actionBarSize"/>
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/app_bar"/>

    <ImageView
        android:id="@+id/detail"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:contentDescription="@null"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/app_bar"/>
</androidx.constraintlayout.widget.ConstraintLayout>

Step 3. Write Code

Finally we need to write our code as follows:

(a). Utils.kt

Our Utils class.

Just copy the code below and replace the package with your app's package name.

@file:Suppress("NOTHING_TO_INLINE")

package replace_with_your_package_name

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import androidx.annotation.LayoutRes
import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor

inline fun <reified V : View> ViewGroup.inflate(
    @LayoutRes layoutRes: Int,
    attachToRoot: Boolean = false
): V = LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot) as V

inline fun WindowInsets.toCompat(): WindowInsetsCompat {
    return WindowInsetsCompat.toWindowInsetsCompat(this)
}

fun <T> DiffUtil.ItemCallback<T>.asConfig(): AsyncDifferConfig<T> {
    return AsyncDifferConfig.Builder(this)
        .setBackgroundThreadExecutor(Dispatchers.IO.asExecutor())
        .build()
}

(b). ImageListAdapter.kt

Our ImageListAdapter class.

Create a Kotlin file named ImageListAdapter.kt.

Add imports from android SDK and other packages. Here are some of the imports we will use in this class:

  1. Color from the android.graphics package.
  2. ColorDrawable from the android.graphics.drawable package.
  3. View from the android.view package.
  4. ViewGroup from the android.view package.
  5. ImageView from the android.widget package.
  6. updateLayoutParams from the androidx.core.view package.
  7. DiffUtil from the androidx.recyclerview.widget package.
  8. ListAdapter from the androidx.recyclerview.widget package.
  9. RecyclerView from the androidx.recyclerview.widget package.

Next create a class that derives from RecyclerView.ViewHolder(itemView) and add its contents as follows:

We will be overriding the following functions:

  1. onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder.
  2. onBindViewHolder(holder: ViewHolder, position: Int).

Just copy the code below and replace the package with your app's package name.

package replace_with_your_package_name

import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.memory.MemoryCache
import coil.sample.ImageListAdapter.ViewHolder

class ImageListAdapter(
    private val numColumns: Int,
    private val setScreen: (Screen) -> Unit
) : ListAdapter<Image, ViewHolder>(Callback.asConfig()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(parent.inflate(R.layout.list_item))
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.image.apply {
            val item = getItem(position)

            updateLayoutParams {
                val size = item.calculateScaledSize(context, numColumns)
                width = size.width
                height = size.height
            }

            var placeholder: MemoryCache.Key? = null

            load(item.uri) {
                placeholder(ColorDrawable(item.color))
                error(ColorDrawable(Color.RED))
                parameters(item.parameters)
                listener { _, result -> placeholder = result.memoryCacheKey }
            }

            setOnClickListener {
                setScreen(Screen.Detail(item, placeholder))
            }
        }
    }

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val image get() = itemView as ImageView
    }

    private object Callback : DiffUtil.ItemCallback<Image>() {
        override fun areItemsTheSame(old: Image, new: Image) = old.uri == new.uri
        override fun areContentsTheSame(old: Image, new: Image) = old == new
    }
}

(c). MainActivity.kt

Our MainActivity class.

Next create a class that derives from AppCompatActivity and add its contents as follows:

We will be overriding the following functions:

  1. onCreate(savedInstanceState: Bundle?).
  2. onCreateOptionsMenu(menu: Menu): Boolean.
  3. onOptionsItemSelected(item: MenuItem): Boolean.

Besides we will be creating the following methods:

  1. setScreen(parameter)- We pass a Screen object as a parameter.
  2. setImages(parameter) - We pass a List<Image> object as a parameter.
  3. setAssetType(parameter) - We pass a AssetType object as a parameter.

Just copy the code below and replace the package with your app's package name.

package replace_with_your_package_name

import android.os.Build.VERSION.SDK_INT
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.activity.OnBackPressedCallback
import androidx.activity.addCallback
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import androidx.recyclerview.widget.StaggeredGridLayoutManager.VERTICAL
import coil.load
import coil.sample.databinding.ActivityMainBinding
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {

    private val viewModel: MainViewModel by viewModels()

    private lateinit var binding: ActivityMainBinding
    private lateinit var listAdapter: ImageListAdapter
    private lateinit var backPressedCallback: OnBackPressedCallback

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        setSupportActionBar(binding.toolbar)

        if (SDK_INT >= 29) {
            WindowCompat.setDecorFitsSystemWindows(window, false)
            binding.toolbar.setOnApplyWindowInsetsListener { view, insets ->
                view.updatePadding(
                    top = insets.toCompat().getInsets(WindowInsetsCompat.Type.systemBars()).top
                )
                insets
            }
        }

        val numColumns = numberOfColumns(this)
        listAdapter = ImageListAdapter(numColumns) { viewModel.screen.value = it }
        binding.list.apply {
            setHasFixedSize(true)
            layoutManager = StaggeredGridLayoutManager(numColumns, VERTICAL)
            adapter = listAdapter
        }

        backPressedCallback = onBackPressedDispatcher.addCallback(enabled = false) {
            viewModel.onBackPressed()
        }

        lifecycleScope.apply {
            launch { viewModel.assetType.collect(::setAssetType) }
            launch { viewModel.images.collect(::setImages) }
            launch { viewModel.screen.collect(::setScreen) }
        }
    }

    private fun setScreen(screen: Screen) {
        when (screen) {
            is Screen.List -> {
                backPressedCallback.isEnabled = false
                binding.list.isVisible = true
                binding.detail.isVisible = false
            }
            is Screen.Detail -> {
                backPressedCallback.isEnabled = true
                binding.list.isVisible = false
                binding.detail.isVisible = true
                binding.detail.load(screen.image.uri) {
                    placeholderMemoryCacheKey(screen.placeholder)
                    parameters(screen.image.parameters)
                }
            }
        }
    }

    private fun setImages(images: List<Image>) {
        listAdapter.submitList(images) {
            // Ensure we're at the top of the list when the list items are updated.
            binding.list.scrollToPosition(0)
        }
    }

    @Suppress("UNUSED_PARAMETER")
    private fun setAssetType(assetType: AssetType) {
        invalidateOptionsMenu()
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        val title = viewModel.assetType.value.name
        val item = menu.add(Menu.NONE, R.id.action_toggle_asset_type, Menu.NONE, title)
        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.action_toggle_asset_type -> {
                viewModel.assetType.value = viewModel.assetType.value.next()
            }
            else -> return super.onOptionsItemSelected(item)
        }
        return true
    }
}

Reference

Download the code below:

No. Link
1. Download Full Code
2. Read more here.
3. Follow code author here.

Coil Image-Loader Android Example

A Sample App for Coil Image Loader Library for Android using Kotlin!....

Here is the GIF demo:

Coil-Image-Loader-And Example Tutorial

Let us look at a full android Coil sample project.

Step 1. Dependencies

We need to add some dependencies in our app/build.gradle file as shown below:

(a). build.gradle

Our app-level build.gradle.

We Prepare our dependencies as shown below. You may use later versions.

At the top of our app/build.gradle we will apply the following 3 plugins:

  1. Our com.android.application plugin.
  2. Our kotlin-android plugin.
  3. Our kotlin-android-extensions plugin.

Inside the android closure we will set the compileSDKVersion and buildToolsVersion. We will also set the application ID, minimum SDK version, target SDK version, version code and version name inside a defaultConfig closure. We will also set the build types: to either debug or release. For each build type you can enable minification of resources, as well as enable proguard.

After that We will enable Java8 so that we can utilize a myriad of java8 features.

Next we will declare our app dependencies in under the dependencies closure. We will need the following 5 dependencies:

  1. Our Kotlin-stdlib-jdk7 library.
  2. Our Appcompat library.
  3. Our Core-ktx library.
  4. Our Constraintlayout library.
  5. Our Coil library.

Here is our full app/build.gradle:

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "www.sanju.coilimageloader"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    kotlinOptions{
        jvmTarget = "1.8"
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'


    // Library
    implementation("io.coil-kt:coil:0.9.1")
}

Step 2. Our Android Manifest

We will need to look at our AndroidManifest.xml.

(a). AndroidManifest.xml

Our AndroidManifest file.

Here we will add the following permission:

  1. Our INTERNET permission.

Here is the full Android Manifest file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="www.sanju.coilimageloader">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Step 3. Design Layouts

In Android we design our UI interfaces using XML. So let's create the following layouts:

(a). activity_main.xml

Our activity_main layout.

Furthermore design your XML layout using the following 2 UI widgets and ViewGroups:

  1. RelativeLayout
  2. ImageView
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    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/image1"
      android:layout_width="300dp"
      android:layout_centerInParent="true"
      android:layout_height="300dp"/>


</RelativeLayout>

Step 4. Write Code

Finally we need to write our code as follows:

(a). MainActivity.kt

Our MainActivity class.

First you create a Kotlin file named MainActivity.kt and add the following code:

package replace_with_your_package_name

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import coil.api.load
import coil.transform.BlurTransformation
import coil.transform.CircleCropTransformation
import coil.transform.GrayscaleTransformation
import coil.transform.RoundedCornersTransformation
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

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

        // Image Url
        val imageUrl = "https://images.unsplash.com/photo-1517849845537-4d257902454a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=675&q=80"


        // Simple Loader
        image1.load(imageUrl)

        // Circle Crop Animation
        image1.load("https://images.unsplash.com/photo-1571233954463-3aafee3889d6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=652&q=80"){
            crossfade(true) // crossfade animation
            crossfade(3000) // animation in seconds ex- 3 seconds
            transformations(CircleCropTransformation()) // circle crop animation
        }

        //Blur Animation
        image1.load("https://images.unsplash.com/photo-1571233954463-3aafee3889d6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=652&q=80"){
            crossfade(true) // crossFade animation
            crossfade(3000) // animation in seconds ex- 3 seconds
            transformations(BlurTransformation(this@MainActivity,3f, 0.3f)) // Blur  animation
        }


        //Circle Crop Animation with Blur Reveal
        image1.load(imageUrl){
            crossfade(true) // crossFade animation
            crossfade(3000) // animation in seconds ex- 3 seconds
            transformations(CircleCropTransformation(),BlurTransformation(this@MainActivity,3f,0.3f)) // Circle Crop animation with blur reveal
        }

        // Customer Rounded Corners
        image1.load("https://images.unsplash.com/photo-1571233954463-3aafee3889d6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=652&q=80"){
            crossfade(true) // crossFade animation
            crossfade(3000) // animation in seconds ex- 3 seconds
            transformations(RoundedCornersTransformation(12f,12f,12f,12f)) // Custom corner radius setter
        }

        //Gray Scale Image
        image1.load("https://images.unsplash.com/photo-1571233954463-3aafee3889d6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=652&q=80") {
            crossfade(true) // crossFade animation
            crossfade(3000) // animation in seconds ex- 3 seconds
            transformations(GrayscaleTransformation()) // Gray Scale Image
        }


        // Load from Drawable
        image1.load(R.drawable.image6)


    }
}

Reference

Download the code below:

No. Link
1. Download Full Code
2. Read more here.
3. Follow code author here.