Glide — Callbacks: SimpleTarget and ViewTarget for Custom View Classes

After the last three tutorials revolved around optimizing the flow of Glide and improving the user experience, the next few tutorials will be about using callback techniques with Glide. So far, we've always assumed we're loading the images or Gifs into an ImageView. But that might not always be the case. In this tutorial, we'll look at ways to get the Bitmap of an image resource without specifying an ImageView.

Glide Series Overview

Callbacks in Glide: Targets

So far we've used the convenient Glide builder to load images into an ImageView. Glide hides a ton of complexity of what happens behind the scenes. Glide does all the network requests and processing in a background thread and, once the result is ready, changes back to the UI thread and updates the ImageView.

In this tutorial, we assume that we won't have an ImageView as the destination for the image. We rather want the Bitmap itself. Glide offers an easy way to access the Bitmap of an image resource with Targets. Targets are nothing else than callbacks, which return the result after Glide's asynchronous thread is done with all the loading and processing.

Glide offers various kinds of targets while each has an explicit purpose. We'll walk through them in the next few sections. We start with the BaseTarget.

BaseTarget

So let's look at a code example:

Glide 4.x

private BaseTarget target = new BaseTarget<BitmapDrawable>() {  
  @Override
  public void onResourceReady(BitmapDrawable bitmap, Transition<? super BitmapDrawable> transition) {
    // do something with the bitmap
    // for demonstration purposes, let's set it to an imageview
    imageView1.setImageDrawable(bitmap);
  }

  @Override
  public void getSize(SizeReadyCallback cb) {
    cb.onSizeReady(SIZE_ORIGINAL, SIZE_ORIGINAL);
  }

  @Override
  public void removeCallback(SizeReadyCallback cb) {}
};

private void loadImageSimpleTarget() {  
  GlideApp
    .with(context) // could be an issue!
    .load(eatFoodyImages[0])
    .into(target);
}

We need to override the getSize callback. We're calling cb.onSizeReady(SIZE_ORIGINAL, SIZE_ORIGINAL); to make sure Glide uses the highest possible resolution. Of course, you might want to pass a specific resolution, for example based on a view's size. We'll look at that a little further down this tutorial.

Glide 3.x

private SimpleTarget target = new SimpleTarget<Bitmap>() {  
    @Override
    public void onResourceReady(Bitmap bitmap, GlideAnimation glideAnimation) {
        // do something with the bitmap
        // for demonstration purposes, let's just set it to an ImageView
        imageView1.setImageBitmap(bitmap);
    }
};

private void loadImageSimpleTarget() {  
    Glide
        .with(context) // could be an issue!
        .load(eatFoodyImages[0] )
        .asBitmap()
        .into( target );
}

The first part of the snippet creates a field object, that declares a function, which is called once Glide has loaded and processed the image. The callback function passes the Bitmap (or a general Drawable, if you don't call .asBitmap()) as a parameter. You can then use the Bitmap object for whatever use you need it.

The second part of the snippet is how we use targets with Glide: exactly the same as with ImageViews! You can pass either, a Target or an ImageView, as parameter to the .into() method. Glide will do its magic and return the result to either one. There is one difference, we added the line .asBitmap(), which forces Glide to return a Bitmap object. Remember, that Glide can also load Gifs or videos. In order to prevent a clash between the target (which expects a Bitmap) and the unknown resource on the Internet behind the URL (which could be a Gif), we can call .asBitmap() to tell Glide to only understand the request as successful, if the resource is an image.

Pay Attention with Targets

Besides knowing how to implement a simple version of Glide's Target callback system, you've learned two additional things.

The first is the field declaration of the BaseTarget object (SimpleTarget for Glide 3.x). Technically, Java/Android would allow you to declare the target anonymously in the .into() method. However, this significantly increases the chance that the Android garbage collector removes the anonymous target object before Glide was done with the image request. Eventually, this would lead to a situation, where the image is loaded, but the callback can never be called. So please make sure you declare your callback objects as field objects, so you're protecting it from the evil Android garbage collector 😉

The second critical part is the Glide builder line .with(context). The issue here is actually a feature of Glide: when you pass a context, for example the current app activity, Glide will automatically stop the request when the requesting activity is stopped. This integration into the app's lifecycle is usually very helpful, but can be difficult to work with, if your target is independent of the app's activity lifecycle. The solution here is to use the application context: .with( context.getApplicationContext()). Then Glide will only kill the image request, when the app is completely stopped itself. Please keep that in mind. Once again, if your request needs to be outside of the activity lifecycle, use the following snippet:

private void loadImageSimpleTargetApplicationContext() {  
    GlideApp
        .with(context.getApplicationContext() ) // safer!
        .load(eatFoodyImages[1] 
        .asBitmap()
        .into(target2);
}

Target with Specific Size

Another potential issue with targets is that they don't have a specific size. If you pass an ImageView as the parameter for .into(), Glide will use the size of the ImageView to limit the size of the image. For example, if the loaded image is 1000x1000 pixels, but the ImageView only 250x250 pixels, Glide will reduce the image to the smaller size to save processing time and memory. Obviously, this doesn't work with targets, since there is no known size. However, if you have a specific size in mind, you can enhance the callback. If you know how large the image should be, you should specify it in your callback declaration to save some memory:

Glide 4.x

private BaseTarget target2 = new BaseTarget<BitmapDrawable>() {  
  @Override
  public void onResourceReady(BitmapDrawable bitmap, Transition<? super BitmapDrawable> transition) {
    // do something with the bitmap
    // for demonstration purposes, let's set it to an imageview
    imageView2.setImageDrawable(bitmap);
  }

  @Override
  public void getSize(SizeReadyCallback cb) {
    cb.onSizeReady(250, 250);
  }

  @Override
  public void removeCallback(SizeReadyCallback cb) {}
};

private void loadImageSimpleTargetApplicationContext() {  
  GlideApp
    .with(context.getApplicationContext()) // safer!
    .load(eatFoodyImages[1])
    .into(target2);
}

As you can see above we're now specifying a size with cb.onSizeReady(250, 250);.

Glide 3.x

private SimpleTarget target2 = new SimpleTarget<Bitmap>( 250, 250 ) {  
    @Override
    public void onResourceReady(Bitmap bitmap, GlideAnimation glideAnimation) {
        imageView2.setImageBitmap( bitmap );
    }
};

private void loadImageSimpleTargetApplicationContext() {  
    Glide
        .with( context.getApplicationContext() ) // safer!
        .load( eatFoodyImages[1] )
        .asBitmap()
        .into( target2 );
}

The only difference in Glide 3.x to the "normal" target declaration is the specification of the size in pixels: new SimpleTarget<Bitmap>( 250, 250 ).

This should give you all the knowledge to implement BaseTargets in your app.

ViewTarget

The reason we can't use an ImageView directly can be various. We've shown you above how to access a Bitmap. Now, we're going one step further. Let's assume you've a Custom View. Glide doesn't support the loading of images into custom views, since there is no way of knowing where the image should be set. However, Glide makes it much easier with ViewTargets.

Let's look at our simple custom view, which extends FrameLayout and internally uses an ImageView and overlays it with a TextView:

public class FutureStudioView extends FrameLayout {  
    ImageView iv;
    TextView tv;

    public void initialize(Context context) {
        inflate( context, R.layout.custom_view_futurestudio, this );

        iv = (ImageView) findViewById( R.id.custom_view_image );
        tv = (TextView) findViewById( R.id.custom_view_text );
    }

    public FutureStudioView(Context context, AttributeSet attrs) {
        super( context, attrs );
        initialize( context );
    }

    public FutureStudioView(Context context, AttributeSet attrs, int defStyleAttr) {
        super( context, attrs, defStyleAttr );
        initialize( context );
    }

    public void setImage(Drawable drawable) {
        iv = (ImageView) findViewById( R.id.custom_view_image );

        iv.setImageDrawable( drawable );
    }
}

You cannot use the regular .into() method of Glide here, since our custom view doesn't extend ImageView. Thus, we've to create a ViewTarget and use that for the .into() method:

Glide 4.x

FutureStudioView customView = (FutureStudioView) findViewById( R.id.custom_view );

viewTarget = new ViewTarget<FutureStudioView, BitmapDrawable>(customView) {  
  @Override
  public void onResourceReady(BitmapDrawable bitmap, Transition<? super BitmapDrawable> transition) {
    this.view.setImage(bitmap);
  }
};

GlideApp  
    .with(context.getApplicationContext()) // safer!
    .load(eatFoodyImages[2])
    .into(viewTarget);

Glide 3x.

FutureStudioView customView = (FutureStudioView) findViewById( R.id.custom_view );

viewTarget = new ViewTarget<FutureStudioView, GlideDrawable>( customView ) {  
  @Override
  public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
    this.view.setImage( resource.getCurrent() );
  }
};

Glide  
        .with( context.getApplicationContext() ) // safer!
        .load( eatFoodyImages[2] )
  .into( viewTarget );

In the target callback method, we use our created function setImage(Drawable drawable) in the custom view class to set the image. Also, make sure that you noticed that we've to pass our custom view in the constructor of the ViewTarget: new ViewTarget<FutureStudioView, BitmapDrawable>( customView ).

This should cover all your needs with custom views. You could also do additional work in the callback. For example, we could have analyzed the incoming Bitmap for the dominant color and set the hex value to the TextView. But we're sure you've something in mind already.

Outlook

In this tutorial, you've learned the fundamentals of targets in Glide. You've learned how to access the Bitmap of an image and how to load images into your own custom views. Did we miss something? Let us know in the comments!

In the next tutorial, we're going to continue with examples of targets when we look at how to load images into notifications and app widgets.

Explore the Library

Find interesting tutorials and solutions for your problems.