In this section, you will find the description of the BeagleImageDownloader class and details of the methods that implement it.


You can register how remote images will be downloaded and populated in ImageView by providing a class that implements this logic, which was added to Beagle 1.3.0

With BeagleImageDownloader being created in the frontend of your Android application, Beagle will know which logic to use to download and configure the Image components with ImagePath.Remote.

Step 1: Add the dependencies

Locate the file build.gradle (Module: app), open it and scroll down until you find the dependencies {} code block.

  1. Copy and paste the line below into the dependencies:

    • implementation ‘androidx.core:core-ktx:1.2.0’
    • implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9’
    • implementation ‘androidx.lifecycle:lifecycle-runtime-ktx:2.2.0’


  2. Click Sync now to synchronize Gradle again.

  3. This dependency is needed, as the class that implements BeagleImageDownloader will import some settings from it.

Step 2: Create the class

import android.util.Log
import android.util.LruCache
import android.widget.ImageView
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

const val BASE_URL = "http://base.url"

class CustomImageDownloader : BeagleImageDownloader {

    private val imageDownloader: ImageDownloader = ImageDownloader()

    override fun download(url: String, imageView: ImageView, rootView: RootView) { {
            rootView.getLifecycleOwner().lifecycleScope.launch(Dispatchers.IO) {
                val bitmap = try {
                    imageDownloader.getRemoteImage(BASE_URL + url, imageView.width, imageView.height)
                } catch (e: Exception) {
                    Log.e("LOG_TAG", e.message ?: "Error when try to download Image")

                bitmap?.let {
                    setImage(imageView, bitmap)

    private suspend fun setImage(view: ImageView, bitmap: Bitmap?) {
        withContext(Dispatchers.Main) {
            view.context?.let {
                view.setImageDrawable(BitmapDrawable(it.resources, bitmap))

internal class ImageDownloader {

    suspend fun getRemoteImage(url: String, contentWidth: Int, contentHeight: Int) : Bitmap? {
        val cacheId = LruImageCache.generateBitmapId(url, contentWidth, contentHeight)

        return withContext(Dispatchers.IO) {
            val bitmapCached = LruImageCache.get(cacheId)

                ?: url.let {
                    downloadBitmap(it, contentWidth, contentHeight).apply {
                        LruImageCache.put(cacheId, this)

    private fun downloadBitmap(url: String?, contentWidth: Int, contentHeight: Int) : Bitmap {
        val inputStream: InputStream = URL(url).openStream()
        return BitmapFactory.decodeStream(inputStream)


internal object LruImageCache {

    private val cache: LruCache<String, Bitmap> by lazy {
        LruCache<String, Bitmap>(anEighthOfMemory())

    fun put(key: String, bitmap: Bitmap?) {
        if (bitmap != null) {
            cache.put(key, bitmap)

    fun get(key: String?): Bitmap? = cache.get(key)

    private fun anEighthOfMemory() = ((Runtime.getRuntime().maxMemory() / 1024).toInt() / 8)

    fun generateBitmapId(
        url: String?,
        contentWidth: Int,
        contentHeight: Int
    ) = StringBuilder().append(url).append(contentWidth).append(contentHeight).toString()

Last modified February 11, 2021: create content (#298) (43225e15)