Coil Examples - Coroutine Image Loader
Coil is a lightweight image loading library for Android that is focused on ease of use, simplicity, and performance. It is designed to load images quickly and efficiently without any hassle. With Coil, you can load images from various sources like network, file system, and even resources.
Why use Coil?
Coil is a great choice for any Android developer who wants to load images in their app without worrying about the complexity of image loading. It is easy to use and provides a lot of features that make image loading a breeze.
Here are some reasons why you should use Coil in your Android app:
- Easy to use: Coil has a simple API that makes it easy to load images in your app.
- Lightweight: Coil is lightweight and doesn't add much to your app's APK size.
- Fast: Coil is designed to load images quickly and efficiently, which means your app's performance won't be affected.
- Supports various image sources: Coil can load images from various sources, including network, file system, and resources.
- Caching: Coil provides a built-in caching mechanism that helps in speeding up image loading.
How to use Coil in your Android app?
To use Coil in your Android app, you need to add the following dependency to your build.gradle file:
implementation 'io.coil-kt:coil:1.4.0'
After adding the dependency, you can start using Coil in your app. Here's an example of how to load an image from a URL using Coil:
val imageView: ImageView = findViewById(R.id.image_view)
val imageUrl = "https://example.com/image.jpg"
Coil.load(imageUrl) {
crossfade(true)
placeholder(R.drawable.placeholder)
error(R.drawable.error)
}.into(imageView)
In this example, we are loading an image from a URL using the load() function provided by Coil. We then chain some additional functions like crossfade(), placeholder(), and error() to provide some additional options for our image loading. Finally, we call the into() function to load the image into our ImageView.
Coil also provides some additional features like transformations, which can be used to apply effects to loaded images. Here's an example of how to apply a transformation to an image:
val imageView: ImageView = findViewById(R.id.image_view)
val imageUrl = "https://example.com/image.jpg"
Coil.load(imageUrl) {
crossfade(true)
placeholder(R.drawable.placeholder)
error(R.drawable.error)
transformations(CircleCropTransformation())
}.into(imageView)
In this example, we are applying a CircleCropTransformation() to the loaded image using the transformations() function provided by Coil.
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
AndroidManifestfile.
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_itemlayout.
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_mainlayout.
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
Utilsclass.
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
ImageListAdapterclass.
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:
Colorfrom theandroid.graphicspackage.ColorDrawablefrom theandroid.graphics.drawablepackage.Viewfrom theandroid.viewpackage.ViewGroupfrom theandroid.viewpackage.ImageViewfrom theandroid.widgetpackage.updateLayoutParamsfrom theandroidx.core.viewpackage.DiffUtilfrom theandroidx.recyclerview.widgetpackage.ListAdapterfrom theandroidx.recyclerview.widgetpackage.RecyclerViewfrom theandroidx.recyclerview.widgetpackage.
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
MainActivityclass.
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 aScreenobject as a parameter.setImages(parameter)- We pass aList<Image>object as a parameter.setAssetType(parameter)- We pass aAssetTypeobject 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. |