Glide Tutorial for Android: Getting Started

In this Glide Tutorial, you’ll learn how to use Glide to create a photo app that displays pictures to your users with different filters. By Meng Taing.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Clearing the Cache

There are some good reasons to clear the cache. One reason is to debug. You want to make sure Glide isn’t loading images from memory or disk. Another reason is to allow users to clear some disk space. Someday, you might build an app that catches up to hundreds of megabytes of images!

To clear Glide’s cache, add the following code to clearCache() at the end of MainActivity.kt:

Thread(Runnable {
  Glide.get(this).clearDiskCache() //1
}).start()
Glide.get(this).clearMemory() //2
  1. You can only call clearDiskCache() in background. A simple solution is to call this method in a thread.
  2. You can only call clearMemory() in the main thread.

The option menu item invokes clearCache(). To verify whether the cache is cleared, open the App Info of Wendergram. On some devices or emulators, you can long-press the app icon and drag it to App Icon on the top of the screen.

As you can see, the cache size now is 9.48MB. Run the app and click Clear Cache from the option menu. Now, go back to App Info to check whether the cache size is smaller.

Now the cache size is 24KB. When you launch the app again, you’ll see some photo placeholders for a brief moment before all the images appear. What does it mean? This means Glide has to load all the photos from the network again.

Displaying the Loading Progress Bar

Depending on network speed and image size, loading an image can take up to a few seconds. Your users might not be patient enough to wait on a static screen. It’s a better user experience to show the loading progress bar for such a long-running task.

Although adding a progress bar doesn’t sound very hard, unfortunately, Glide doesn’t expose a download progress listener. In order to listen to Glide’s downloading progress, you need to override the Glide module and provide your own OkHttp integration.

Adding Glide Annotation and OkHttp Integration

Add the following dependency to build.gradle file in the app directly:

kapt 'com.github.bumptech.glide:compiler:4.9.0'
implementation('com.github.bumptech.glide:okhttp3-integration:4.9.0') {
  exclude group: 'glide-parent'
}

Also, add the following line to the top of the same build.gradle file, below other plugins:

apply plugin: 'kotlin-kapt'

kapt is an annotation processor for Kotlin. Don’t mistakenly use annotationProcessor because if you do, your new custom Glide module won’t be used, and it won’t throw any errors.

Sync the project with Gradle file. Grab a coffee before going through the final stretch of the tutorial. There will be a lot of code copying and pasting and explanations. :]

Creating ResponseProgressListener

You’ll have to create multiple interfaces and classes in order to make this work, so create a new package under the root package and name it glide.

Under the glide pacakge you created above, create a new interface named ResponseProgressListener:

interface ResponseProgressListener {
  fun update(url: HttpUrl, bytesRead: Long, contentLength: Long)
}

This interface is responsible for notifying whoever is listening to the downloading progress of the URL.

Creating UIonProgressListener

In the same package as above, create a new interface named UIonProgressListener:

interface UIonProgressListener {
  
  val granularityPercentage: Float //1

  fun onProgress(bytesRead: Long, expectedLength: Long) //2
}

As the name suggests, this interface is responsible for updating the UI, which updates the progress of the ProgressBar.

granularityPercentage controls how often the listener needs an update. 0% and 100% will always be dispatched. For example, if you return one, it will dispatch at most 100 times, with each time representing at least one percent of the progress.

Creating DispatchingProgressManager

You need to create class which not only keeps track of the progress of all URLs but also notifies the UI listeners to update progress in the UI thread. Create a new class named DispatchingProgressManager in the same glide package:

class DispatchingProgressManager internal constructor() : ResponseProgressListener {

  companion object {
    private val PROGRESSES = HashMap<String?, Long>() //1
    private val LISTENERS = HashMap<String?, UIonProgressListener>() //2

    internal fun expect(url: String?, listener: UIonProgressListener) { //3
      LISTENERS[url] = listener
    }

    internal fun forget(url: String?) { //4
      LISTENERS.remove(url)
      PROGRESSES.remove(url)
    }
  }

  private val handler: Handler = Handler(Looper.getMainLooper()) //5

  override fun update(url: HttpUrl, bytesRead: Long, contentLength: Long) {
    val key = url.toString()
    val listener = LISTENERS[key] ?: return //6
    if (contentLength <= bytesRead) { //7
      forget(key)
    }
    if (needsDispatch(key, bytesRead, contentLength, 
        listener.granularityPercentage)) { //8
      handler.post { listener.onProgress(bytesRead, contentLength) }
    }
  }

  private fun needsDispatch(key: String, current: Long, total: Long, granularity: Float): Boolean {
    if (granularity == 0f || current == 0L || total == current) {
      return true
    }
    val percent = 100f * current / total
    val currentProgress = (percent / granularity).toLong()
    val lastProgress = PROGRESSES[key]
    return if (lastProgress == null || currentProgress != lastProgress) { //9
      PROGRESSES[key] = currentProgress
      true
    } else {
      false
    }
  }
}
  1. You need a HashMap to store the progress of the URLs you want to display the ProgressBar.
  2. You also need another HashMap to store the UI listeners.
  3. This is the method to add the URL and its UI listener to the HashMap. You call this at the beginning of the download.
  4. When the download of a URL is completed or returns an error, call this to remove the URL from both HashMaps.
  5. You need a UI thread handler to update the UI because the progress is notified in the background thread.
  6. Not all the URLs must have a progress listener. You don't need to show ProgressBar for a photo thumbnail.
  7. Yay! Download completed. Forget this URL.
  8. Remember the granularityPercentage in UIonProgressListener interface? This is where you decide whether to update the UI if downloaded content length is worthy; i.e., not smaller than the granularity.
  9. Here is the simple explanation. You get the currentProgress by dividing the current percent by the granularityPercentage value. Get the lastProgress from the HashMap, and compare currentProgress and lastProgress to see if there's any change; i.e., currentProgress is greater than lastProgress by the multiple of granularityPercentage. If that's the case, notify UI listener.

Listening to Progress in the ResponseBody of OkHttp

If you've used Retrofit with OkHttpClient, you know you can intercept the request and rebuild the response because you want to append an Authorization header with an access toke. In this case, you can wrap the DispatchingProgressManager into your custom ResponseBody.

Create a new class named OkHttpProgressResponseBody in the glide package with the following code:

class OkHttpProgressResponseBody internal constructor(
    private val url: HttpUrl,
    private val responseBody: ResponseBody,
    private val progressListener: ResponseProgressListener) : ResponseBody() { //1

  //2
  private var bufferedSource: BufferedSource? = null

  //3
  override fun contentType(): MediaType {
    return responseBody.contentType()!!
  }

  //4
  override fun contentLength(): Long {
    return responseBody.contentLength()
  }

  //5
  override fun source(): BufferedSource {
    if (bufferedSource == null) {
      bufferedSource = Okio.buffer(source(responseBody.source()))
    }
    return this.bufferedSource!!
  }

  //6
  private fun source(source: Source): Source {
    return object : ForwardingSource(source) {
      var totalBytesRead = 0L

      @Throws(IOException::class)
      override fun read(sink: Buffer, byteCount: Long): Long {
        val bytesRead = super.read(sink, byteCount)
        val fullLength = responseBody.contentLength()
        if (bytesRead.toInt() == -1) { // this source is exhausted
          totalBytesRead = fullLength
        } else {
          totalBytesRead += bytesRead
        }
        progressListener.update(url, totalBytesRead, fullLength)  //7
        return bytesRead
      }
    }
  }
}
  1. Take the original ResponseBody of OkHttp and return a new ResponseBody, which has your listener embedded in.
  2. Create your own BufferedSource to read the downloaded byte length.
  3. Nothing special here. Just return the content type of the original ResponseBody.
  4. Return the content length of the original ResponseBody.
  5. Recreate the BufferedSource of your own with source(source: Source): Source below.
  6. This is the key part of the progress listening section. This method returns a new Source, which keeps track of totalBytesRead and dispatches it to the listener.
Meng Taing

Contributors

Meng Taing

Author

Aldo Olivares

Tech Editor

Nicole Hardina

Editor

Luke Freeman

Illustrator

Jenn Bailey

Final Pass Editor

Eric Soto

Team Lead

Over 300 content creators. Join our team.