After you've learned how to load images from all kinds of sources and how to display them in ListViews, this tutorial is all about placeholders, which bridge the time until the image loading.
Glide Series Overview
Placeholders with .placeholder()
We probably don't even have to explain or discuss it: empty ImageView
s don't look good in any UI. If you're using Glide, you most likely are loading images via an Internet connection. Depending on your user's environment, this might take a significant amount of time. An expected behavior of an app is to display a placeholder until the image is loaded and processed.
Glide's fluent interface makes this very easy to do! Just call .placeHolder()
with a reference to a drawable (resource) and Glide will display that as a placeholder until your actual image is ready. Keep in mind that this process is done on the UI-thread. Thus, don't use giant images for the placeholder. Instead, use smaller images which are fast to load as placeholders:
Glide 4.x
GlideApp
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.placeholder(R.mipmap.ic_launcher) // can also be a drawable
.into(imageViewPlaceholder);
Glide 3.x
Glide
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.placeholder(R.mipmap.ic_launcher) // can also be a drawable
.into(imageViewPlaceholder);
For obvious reasons, you cannot set an Internet url as placeholder, since that one would be required to be loaded as well. App resources and drawables are guaranteed to be available, accessible and relatively efficient. However, as the parameter of the load()
method, Glide accepts all kind of values. These might not be loadable (no Internet connection, server down, ...), deleted or not accessible. In the next section, we'll talk about an error placeholder.
Error Placeholder with .error()
Let's assume our app tries to load an image from a website, which is currently down. Glide does give us the option to get an error callback and take the appropriate action. While we'll cover that option later, for now this would be too complicated. In most use cases a placeholder, which signals that the image could not be loaded is sufficient enough.
The call to Glide's fluent interface is identical to the previous example for our pre-display placeholder, just with a different function call named error()
:
Glide 4.x
GlideApp
.with(context)
.load("http://futurestud.io/non_existing_image.png")
.placeholder(R.mipmap.ic_launcher) // can also be a drawable
.error(R.mipmap.future_studio_launcher) // will be displayed if the image cannot be loaded
.into(imageViewError);
Glide 3.x
Glide
.with(context)
.load("http://futurestud.io/non_existing_image.png")
.placeholder(R.mipmap.ic_launcher) // can also be a drawable
.error(R.mipmap.future_studio_launcher) // will be displayed if the image cannot be loaded
.into(imageViewError);
That's it. If the image you define as load()
value cannot be loaded, Glide will display R.mipmap.future_studio_launcher
instead. Once again, acceptable parameters for error()
are only already initialized drawable objects or pointers to their resources (R.drawable.<drawable-keyword>
).
Dynamic Error Placeholder
Starting with Glide 4.3.0
you can also pass a full Glide request to the error()
method. That means you don't have to provide an error placeholder with the phone. Instead, you could load any other image from the Internet.
For example, your app might try to load a profile image of a user. If the request doesn't work out, you can try a backup image from a different URL, instead of just serving a generic placeholder. The code is straightforward:
GlideApp
.with(context)
.load("http://futurestud.io/non_existing_image.png")
.error(
GlideApp
.with(context)
.load("http://futurestud.io/fallback_image.png")
)
.into(imageViewError);
You pass a full Glide request to the error()
method. The passed Glide request has all options available, so you could also add transformations, animations, etc.
.fallback()
Placeholder
The .error()
placeholder is triggered when the resource is not available, for example the image link is down. Another type of “error” is if you pass a null
value to Glide. For example, this could happen if you’ve a ListView with profile images. Not every profile has an image set and thus you’re passing a null
value. If you want to specify a good replacement image when you pass null
, use .fallback()
:
Glide 4.x
String nullString = null; // could be set to null dynamically
GlideApp
.with(context)
.load( nullString )
.fallback( R.drawable.floorplan )
.into( imageViewNoFade );
Glide 3.x
String nullString = null; // could be set to null dynamically
Glide
.with(context)
.load( nullString )
.fallback( R.drawable.floorplan )
.into( imageViewNoFade );
The rules are identical to .placeholder()
: you can only pass a drawable or a resource id.
Transitions
No matter if you're displaying a placeholder before loading the image or not, changing an image of an ImageView
is a pretty significant change in your UI. A simple option to make this change more smoothly and easier on the eye, is to use a crossfade animation. Glide ships with a standard crossfade animation, which is active by default for Glide 3.6.1 until Glide 3.8.0. If you want to force Glide to show the crossfade animation, all you've to do is another call on the builder:
Glide 4.x
GlideApp
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.placeholder(R.mipmap.ic_launcher) // can also be a drawable
.error(R.mipmap.future_studio_launcher) // will be displayed if the image cannot be loaded
.transition(DrawableTransitionOptions.withCrossFade())
.into(imageViewCombined);
Glide 3.x
Glide
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.placeholder(R.mipmap.ic_launcher) // can also be a drawable
.error(R.mipmap.future_studio_launcher) // will be displayed if the image cannot be loaded
.crossFade()
.into(imageViewFade);
The withCrossFade
, or crossFade()
methods have another method signature: (int duration)
. If you want to slow down (or speed up) the animation, feel free to pass a time in milliseconds to the methods. The default duration of the transition is 300 milliseconds.
Glide 3.x: Use of dontAnimate()
If you're using Glide 3.x and wish to directly display the image without the small crossfade effect, call .dontAnimate()
on the Glide request builder:
Glide
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.placeholder(R.mipmap.ic_launcher) // can also be a drawable
.error(R.mipmap.future_studio_launcher) // will be displayed if the image cannot be loaded
.dontAnimate()
.into(imageViewFade);
This would directly show you the image, without fading it into the ImageView
. Please make sure you've a good reason for doing this though!
Combining Everything
It is important to know that all these parameters are independent and can be set without relying on each other. For example, you could just set .error()
without calling .placeholder()
. You could set the crossfade animations without the placeholders. Any combination of the parameters is possible.
Outlook
Hopefully you learned a lot from this tutorial. It is tremendously important for a good user experience that the images are not popping in unexpectedly. Also, make it obvious to the user when something goes wrong. Glide assists you with easy-to-call functions, which provide the things you need to design a better app.
Don't forget to check out the official documentation, which also covers placeholders and transitions.
In the context of this tutorial series, we're not done with the optimization options yet. In the next tutorial, we'll look at image resizing and scaling.