Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Uncoiling the COIL - Understanding modern image loading on Android

Uncoiling the COIL - Understanding modern image loading on Android

Image loading is hard but luckily this problem has already been addressed on Android. There are many libraries out there that handle image loading seamlessly. COIL is the new kid to the club and you may be wondering why do we need another image loading library?

COIL is Kotlin first library backed by Kotlin Coroutines. It gives you an easy and concise API to deal with image loading. Oh and also it takes less space! In a nutshell, it's an easy to use, fast, lightweight, and modern library.

This talk will address
- Why you should consider this library for image loading?
- Understanding its API.
- How it works? by covering its entire image loading pipeline.

By the end, you'll walk away with the advantages of using COIL and its image loading pipeline.

Sagar Viradiya

June 26, 2021
Tweet

More Decks by Sagar Viradiya

Other Decks in Programming

Transcript

  1. Uncoiling the COIL
    @viradiya_sagar gdgmad.com
    @gdgmad
    Understanding modern image loading on Android

    View Slide

  2. Agenda
    ● What is COIL?
    ● Why COIL?
    ● How COIL?
    ● Image loading pipeline
    ● In memory caching

    View Slide

  3. What is COIL?

    View Slide

  4. What is COIL?
    ● COroutine Image Loader

    View Slide

  5. What is COIL?
    ● COroutine Image Loader
    ● An image loading library for android (Another? 🤔)

    View Slide

  6. Why COIL?
    ● Modern

    View Slide

  7. Why COIL?
    ● Modern
    ● Easy to use

    View Slide

  8. Why COIL?
    ● Modern
    ● Easy to use
    ● Lightweight

    View Slide

  9. Why COIL?
    ● Modern
    ● Easy to use
    ● Lightweight
    ● Fast

    View Slide

  10. API
    val imageRequest = ImageRequest.Builder(context)
    .data("https:////w.example.com/image.jpg")
    .crossfade(true)
    .target(imageView)
    .build()

    View Slide

  11. API
    val imageLoader = context.imageLoader
    OR
    val imageLoader = ImageLoader.Builder(context)
    .availableMemoryPercentage(0.25)
    .crossfade(true)
    .build()

    View Slide

  12. API
    val disposable = imageLoader.enqueue(imageRequest)

    View Slide

  13. Kotlin syntactic sugar
    // URL
    imageView.load("https:////w.example.com/image.jpg")
    // Resource
    imageView.load(R.drawable.image)
    // File
    imageView.load(File("/path/to/image.jpg"))
    // And more//.

    View Slide

  14. Kotlin syntactic sugar
    imageView.load("https:////w.example.com/image.jpg")

    View Slide

  15. Kotlin syntactic sugar
    imageView.load("https:////w.example.com/image.jpg") {
    crossfade(true)
    placeholder(R.drawable.image)
    transformations(CircleCropTransformation())
    }

    View Slide

  16. Imperative style
    // Coroutine scope
    val request = ImageRequest.Builder(context)
    .data("https:////w.example.com/image.jpg")
    .build()
    val result = imageLoader.execute(request) // Suspends the execution
    val drawable = result.drawable

    View Slide

  17. Jetpack Compose Support
    Image(
    painter = rememberImagePainter("https:////w.example.com/image.jpg"),
    contentDescription = null,
    modifier = Modifier.size(128.dp)
    )

    View Slide

  18. Jetpack Compose Support
    Image(
    painter = rememberImagePainter(
    data = "https:////w.example.com/image.jpg",
    builder = {
    transformations(CircleCropTransformation())
    crossfade(true)
    }
    ),
    contentDescription = null,
    modifier = Modifier.size(128.dp)
    )

    View Slide

  19. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  20. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  21. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  22. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  23. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  24. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  25. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  26. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  27. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  28. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  29. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  30. ImageLoader
    ● A service class responsible for executing and managing
    entire pipeline.

    View Slide

  31. ImageLoader
    ● A service class responsible for executing and managing
    entire pipeline.
    ● Takes ImageRequest.

    View Slide

  32. ImageLoader
    ● A service class responsible for executing and managing
    entire pipeline.
    ● Takes ImageRequest.
    ● COIL provides singleton (Recommended)

    View Slide

  33. ImageLoader
    // enqueue
    val request = ImageRequest.Builder(context)
    .data("https:////w.example.com/image.jpg")
    .target(imageView)
    .build()
    val disposable = imageLoader.enqueue(request)

    View Slide

  34. ImageLoader
    // execute
    val request = ImageRequest.Builder(context)
    .data("https:////w.example.com/image.jpg")
    .build()
    val result = imageLoader.execute(request) // Suspends the execution

    View Slide

  35. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  36. Interceptors

    View Slide

  37. Interceptors
    ● Allows you to observe, transform, short circuit or retry
    request

    View Slide

  38. Interceptors
    ● Allows you to observe, transform, short circuit or retry
    request
    ● You can wrap the pipeline with your custom logic

    View Slide

  39. Interceptors
    ● Allows you to observe, transform, short circuit or retry
    request
    ● You can wrap the pipeline with your custom logic
    ● Inspired from OKHttp’s interceptor

    View Slide

  40. class CustomCacheInterceptor(
    private val context: Context,
    private val cache: LruCache
    ) : Interceptor {
    override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
    val value = cache.get(chain.request.data.toString())
    if (value /= null) {
    return SuccessResult(
    drawable = value.bitmap.toDrawable(context),
    request = chain.request,
    metadata = TODO()
    )
    }
    return chain.proceed(chain.request)
    }
    }

    View Slide

  41. class CustomCacheInterceptor(
    private val context: Context,
    private val cache: LruCache
    ) : Interceptor {
    override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
    val value = cache.get(chain.request.data.toString())
    if (value /= null) {
    return SuccessResult(
    drawable = value.bitmap.toDrawable(context),
    request = chain.request,
    metadata = TODO()
    )
    }
    return chain.proceed(chain.request)
    }
    }

    View Slide

  42. class CustomCacheInterceptor(
    private val context: Context,
    private val cache: LruCache
    ) : Interceptor {
    override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
    val value = cache.get(chain.request.data.toString())
    if (value /= null) {
    return SuccessResult(
    drawable = value.bitmap.toDrawable(context),
    request = chain.request,
    metadata = TODO()
    )
    }
    return chain.proceed(chain.request)
    }
    }

    View Slide

  43. class CustomCacheInterceptor(
    private val context: Context,
    private val cache: LruCache
    ) : Interceptor {
    override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
    val value = cache.get(chain.request.data.toString())
    if (value /= null) {
    return SuccessResult(
    drawable = value.bitmap.toDrawable(context),
    request = chain.request,
    metadata = TODO()
    )
    }
    return chain.proceed(chain.request)
    }
    }

    View Slide

  44. class CustomCacheInterceptor(
    private val context: Context,
    private val cache: LruCache
    ) : Interceptor {
    override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
    val value = cache.get(chain.request.data.toString())
    if (value /= null) {
    return SuccessResult(
    drawable = value.bitmap.toDrawable(context),
    request = chain.request,
    metadata = TODO()
    )
    }
    return chain.proceed(chain.request)
    }
    }

    View Slide

  45. Plugin custom interceptor
    val imageLoader = ImageLoader.Builder(context)
    .componentRegistry {
    add(CustomCacheInterceptor())
    }
    .build()

    View Slide

  46. EngineInterceptor
    ● The last interceptor in the chain

    View Slide

  47. EngineInterceptor
    ● The last interceptor in the chain
    ● Responsible for checking in memory cache

    View Slide

  48. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  49. Mappers
    ● Allows you to add support for custom data types

    View Slide

  50. Mapper - Example
    data class Item(
    val id: Int,
    val imageUrl: String,
    val price: Int,
    val weight: Double
    )
    class ItemMapper : Mapper {
    override fun map(data: Item) = data.imageUrl
    }

    View Slide

  51. Mapper - Example
    data class Item(
    val id: Int,
    val imageUrl: String,
    val price: Int,
    val weight: Double
    )
    class ItemMapper : Mapper {
    override fun map(data: Item) = data.imageUrl
    }

    View Slide

  52. Mapper - Example
    imageView.load(item)

    View Slide

  53. Plugin custom mapper
    val imageLoader = ImageLoader.Builder(context)
    .componentRegistry {
    add(ItemMapper())
    }
    .build()

    View Slide

  54. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  55. Fetcher
    ● Translates data into either BufferedSource or Drawable

    View Slide

  56. Fetcher
    ● Translates data into either BufferedSource or Drawable
    ● Built-in fetchers
    ○ AssetUriFetcher
    ○ ContentUriFetcher
    ○ FileFetcher
    ○ HttpFetcher

    View Slide

  57. Custom Fetcher
    interface Fetcher {
    fun handles(data: T): Boolean = true
    fun key(data: T): String?
    suspend fun fetch(
    pool: BitmapPool,
    data: T,
    size: Size,
    options: Options
    ): FetchResult
    }

    View Slide

  58. Plugin custom fetcher
    val imageLoader = ImageLoader.Builder(context)
    .componentRegistry {
    add(MyFetcher())
    }
    .build()

    View Slide

  59. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  60. Decoder
    ● Converts BufferedSource to Drawable

    View Slide

  61. Decoder
    ● Converts BufferedSource to Drawable
    ● Built-in format support
    ○ JPEG
    ○ PNG
    ○ WebPs

    View Slide

  62. Decoder
    ● Converts BufferedSource to Drawable
    ● Built-in format support
    ○ JPEG
    ○ PNG
    ○ WebPs
    ● Extensions
    ○ SVG
    ○ GIFs/HEIFs
    ○ Video frame

    View Slide

  63. Custom Decoder
    interface Decoder {
    fun handles(source: BufferedSource, mimeType: String?): Boolean
    suspend fun decode(
    pool: BitmapPool,
    source: BufferedSource,
    size: Size,
    options: Options
    ): DecodeResult
    }

    View Slide

  64. Plugin custom decoder
    val imageLoader = ImageLoader.Builder(context)
    .componentRegistry {
    add(MyDecoder())
    }
    .build()

    View Slide

  65. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  66. Transformation
    ● Allows you to change the pixel data.

    View Slide

  67. Transformation
    ● Allows you to change the pixel data.
    ● Built-in transformations
    ○ Blur
    ○ Circle crop
    ○ Grayscale
    ○ Rounded corner

    View Slide

  68. Custom Transformation
    interface Transformation {
    fun key(): String
    suspend fun transform(pool: BitmapPool, input: Bitmap, size: Size): Bitmap
    }

    View Slide

  69. Plugin custom transformation
    val imageLoader = ImageLoader.Builder(context)
    .componentRegistry {
    add(MyTransformation())
    }
    .build()

    View Slide

  70. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  71. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  72. The pipeline
    ImageLoader Interceptors
    ImageRequest
    Mappers Fetcher
    Decoder
    Transformation
    ImageResult
    SuccessResult ErrorResult

    View Slide

  73. Thread
    Interceptor
    Mapper
    Fetcher
    Decoder
    Transformation
    Transition
    Main Thread
    Worker Thread
    Main Thread

    View Slide

  74. In memory caching

    View Slide

  75. In memory caching
    ● Strong Memory Cache

    View Slide

  76. In memory caching
    ● Strong Memory Cache
    ● Weak Memory Cache

    View Slide

  77. Engine
    Interceptor

    View Slide

  78. Engine
    Interceptor
    Is in strong
    memory?
    Key

    View Slide

  79. Engine
    Interceptor
    Is in strong
    memory?
    Key
    Yes
    Return Bitmap
    and short
    circuit image
    loading pipeline

    View Slide

  80. Engine
    Interceptor
    Is in strong
    memory?
    Key
    Yes
    Return Bitmap
    and short
    circuit image
    loading pipeline
    No
    Is in
    weak
    memory?

    View Slide

  81. Engine
    Interceptor
    Is in strong
    memory?
    Key
    Yes
    Return Bitmap
    and short
    circuit image
    loading pipeline
    No
    Is in
    weak
    memory?
    Yes
    Return Bitmap
    and short
    circuit image
    loading pipeline

    View Slide

  82. Engine
    Interceptor
    Is in strong
    memory?
    Key
    Yes
    Return Bitmap
    and short
    circuit image
    loading pipeline
    No
    Is in
    weak
    memory?
    No
    Execute Image
    loading
    pipeline
    Yes
    Return Bitmap
    and short
    circuit image
    loading pipeline

    View Slide

  83. Strong Memory Cache
    ● Backed by LRU cache

    View Slide

  84. Strong Memory Cache
    ● Backed by LRU cache
    ● Size of the cache is computed dynamically

    View Slide

  85. Strong Memory Cache
    Req.
    Image 1
    Strong Memory Cache Weak Memory Cache

    View Slide

  86. Strong Memory Cache
    Req.
    Image 1
    Strong Memory Cache Weak Memory Cache
    Image 1

    View Slide

  87. Strong Memory Cache
    Req.
    Image 2
    Strong Memory Cache Weak Memory Cache
    Image 1
    Image 2

    View Slide

  88. Strong Memory Cache
    Req.
    Image 3
    Strong Memory Cache Weak Memory Cache
    Image 1
    Image 2
    Image 3

    View Slide

  89. Strong Memory Cache
    Req.
    Image 4
    Strong Memory Cache Weak Memory Cache
    Image 1
    Image 2
    Image 3
    Image 4

    View Slide

  90. Weak Memory Cache
    ● Backed by HashMap

    View Slide

  91. Weak Memory Cache
    ● Backed by HashMap
    ● Periodically removes bitmap after 10 set operations

    View Slide

  92. Summary
    ● COIL - Coroutine Image Loader
    ● Modern, Easy, Lightweight and Fast
    ● API
    ● Image loading pipeline
    ● In memory caching

    View Slide

  93. Resources
    ● Library documentation
    https://coil-kt.github.io/coil/
    ● ADB Podcast
    http://androidbackstage.blogspot.com/2020/11/episode-151-
    image-loading-with-coil.html

    View Slide

  94. Thank you!

    View Slide

  95. Questions?

    View Slide