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:
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:
To load an image, use the AsyncImage
composable:
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:
ImageLoader
s 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):
If you do not want the singleton ImageLoader
, depend on `io.coil-kt:coil
-baseinstead of
io.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:
- Our ACCESS_NETWORK_STATE permission.
- 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:
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:
androidx.constraintlayout.widget.ConstraintLayout
- Our root elementcom.google.android.material.appbar.AppBarLayout
- Our AppBar layoutcom.google.android.material.appbar.MaterialToolbar
- Our toolbarandroidx.recyclerview.widget.RecyclerView
- A recyclerViewImageView
<?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:
Color
from theandroid.graphics
package.ColorDrawable
from theandroid.graphics.drawable
package.View
from theandroid.view
package.ViewGroup
from theandroid.view
package.ImageView
from theandroid.widget
package.updateLayoutParams
from theandroidx.core.view
package.DiffUtil
from theandroidx.recyclerview.widget
package.ListAdapter
from theandroidx.recyclerview.widget
package.RecyclerView
from theandroidx.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:
onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder
.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:
onCreate(savedInstanceState: Bundle?)
.onCreateOptionsMenu(menu: Menu): Boolean
.onOptionsItemSelected(item: MenuItem): Boolean
.
Besides we will be creating the following methods:
setScreen(parameter)
- We pass aScreen
object as a parameter.setImages(parameter)
- We pass aList<Image>
object as a parameter.setAssetType(parameter)
- We pass aAssetType
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:
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:
- Our
com.android.application
plugin. - Our
kotlin-android
plugin. - 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:
- Our
Kotlin-stdlib-jdk7
library. - Our
Appcompat
library. - Our
Core-ktx
library. - Our
Constraintlayout
library. - 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:
- 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:
RelativeLayout
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. |