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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Glide Tutorial for Android: Getting Started
25 mins
- Getting Started
- Adding Permission and Dependencies
- Photo Source
- Loading a Photo Gallery
- Loading a Profile Picture
- Editing Photos with More Transformations
- Clearing the Cache
- Displaying the Loading Progress Bar
- Adding Glide Annotation and OkHttp Integration
- Creating ResponseProgressListener
- Creating UIonProgressListener
- Creating DispatchingProgressManager
- Listening to Progress in the ResponseBody of OkHttp
- Extending a Glide Module
- Wiring Everything Together
- Applying ProgressListener to PhotoViewHolder
- Where to Go From Here?
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
- You can only call
clearDiskCache()
in background. A simple solution is to call this method in a thread. - 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
}
}
}
- You need a
HashMap
to store the progress of the URLs you want to display theProgressBar
. - You also need another
HashMap
to store the UI listeners. - This is the method to add the URL and its UI listener to the
HashMap
. You call this at the beginning of the download. - When the download of a URL is completed or returns an error, call this to remove the URL from both
HashMap
s. - You need a UI thread handler to update the UI because the progress is notified in the background thread.
- Not all the URLs must have a progress listener. You don't need to show
ProgressBar
for a photo thumbnail. - Yay! Download completed. Forget this URL.
- Remember the
granularityPercentage
inUIonProgressListener
interface? This is where you decide whether to update the UI if downloaded content length is worthy; i.e., not smaller than the granularity. - Here is the simple explanation. You get the
currentProgress
by dividing the current percent by thegranularityPercentage
value. Get thelastProgress
from theHashMap
, and comparecurrentProgress
andlastProgress
to see if there's any change; i.e.,currentProgress
is greater thanlastProgress
by the multiple ofgranularityPercentage
. 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
}
}
}
}
- Take the original
ResponseBody
ofOkHttp
and return a newResponseBody
, which has your listener embedded in. - Create your own
BufferedSource
to read the downloaded byte length. - Nothing special here. Just return the content type of the original
ResponseBody
. - Return the content length of the original
ResponseBody
. - Recreate the
BufferedSource
of your own withsource(source: Source): Source
below. - This is the key part of the progress listening section. This method returns a new
Source
, which keeps track oftotalBytesRead
and dispatches it to the listener.