ZOFTINO.COM android and web dev tutorials

Android Image Loading Using Glide Library

There are many pitfalls when loading images in Android such as memory issues, slow image loading and unresponsive UI which can be handled with image loading library Glide. Glide provides many other features in addition to fetching them efficiently such as resizing images, rotating, cropping, decoding different types of media, transformations, transitions, displaying video still images and memory and disk caching. The main aim of the glide is to provide smooth scrolling experience for users when list of images are scrolled through.

In this tutorial, you can learn about Glide and its features with examples

Setup

To use Glide in your project, you need to add below dependencies to your app’s build.gradle file.

  implementation 'com.github.bumptech.glide:glide:4.6.1'
  annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1' 

Loading Image to ImageView

To simply load an image to ImageView, call with() method on Glide class passing Activity or Fragment, then call load() method on resulting RequestManager object passing image URL string, and finally call into method on resulting RequestBuilder object passing image view object.

 Glide.with(this)
.load(productImage)
.into(imgView); 

Glide class has overloaded with() methods which allow you to tie image loading to activity or fragment lifecycles. In Glide, the image-loading process is life-cycle aware which enable it to give priority to image loading requests of foreground activity or fragment and to clear all image loading requests of an activity or a fragment when the activity or fragment is destroyed.

Loading Image to Target

If you want to do something with the loaded image, you need to load the request into target instead of view. You can use Glide provided SimpleTarget class which implements Target or create custom Target class to capture downloaded image.

 Glide.with(this)
        .load(productImage)
        .into(new SimpleTarget<Drawable>() {
            @Override
            public void onResourceReady(@NonNull Drawable drawable,
                                        @Nullable Transition<? super Drawable>
                                                transition) {
               captureDrawable = drawable;
            }
        });

Custom Glide Object

In the example above we used default singleton Glide object for loading images. Glide allows you to build custom Glide object using GlideBuilder and set the created Glide object as global singleton object using init() method on Glide class.

GlideBuilder has various methods to set bitmap pool, array pool, request options, transition options, disk cache, memory cache, and executor service.

 private void buildGlide(){
    GlideBuilder gb = new GlideBuilder();

    //set mem cache size to 8% of available memory
    LruResourceCache lruMemCache = new LruResourceCache(getMemCacheSize(8));
    gb.setMemoryCache(lruMemCache);

    //set disk cache 300 mb
    InternalCacheDiskCacheFactory diskCacheFactory =
            new InternalCacheDiskCacheFactory(this, 300);
    gb.setDiskCache(diskCacheFactory);

    //set BitmapPool with 1/10th of memory cache's size
    LruBitmapPool bitmapPool = new LruBitmapPool(getMemCacheSize(8)/10);
    gb.setBitmapPool(bitmapPool);

    //set custom Glide as global singleton
    Glide.init(this, gb);
}
private int getMemCacheSize(int percent){
    ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
    ((ActivityManager)
            getSystemService(ACTIVITY_SERVICE)).getMemoryInfo(mi);

    double availableMemory= mi.availMem;
    return (int)(percent*availableMemory/100);
}

Place Holders

You can set an image as place holder that will be displayed while an image loading request is being processed by Glide or if loading of the image fails. To set placeholder and error images, you need to instantiate RequestOptions object, set placeholder and error images by calling placeholder()and error() methods on it. Then add RequestOptions object to RequestManager by calling applyDefaultRequestOptions() method on RequestManager as shown below.

 RequestOptions ro = new RequestOptions();
ro.placeholder(R.drawable.account);
ro.error(R.drawable.account_balance);

Glide.with(this)
        .applyDefaultRequestOptions(ro)
        .load(productImage)
        .into(imgView);

Setting Request Options

You can set request options to image loading request using RequestOptions object and adding it to RequestManager by calling applyDefaultRequestOptions() method on RequestManager object. Some of the important setting are center crop, circle crop, disk cache strategy, disable transitions, compress or encode format, image decode format and priority which can be set using centerCrop(), circleCrop(), diskCacheStrategy(), dontTransform(), encodeFormat(), format() and priority() method respectively.

You can set transformation option by calling transform() method and passing an instance of one of the Transformation implementations such as BitmapTransformation, CenterCrop, CenterInside, CircleCrop, DrawableTransformation, FitCenter,RoundedCorners, and UnitTransformation.

 RequestOptions ro = new RequestOptions();
ro.circleCrop();
ro.diskCacheStrategy(DiskCacheStrategy.ALL);
ro.dontAnimate();
ro.dontTransform();
ro.encodeFormat(Bitmap.CompressFormat.PNG);
ro.format(DecodeFormat.PREFER_ARGB_8888);
ro.priority(Priority.HIGH);

Glide.with(this)
        .applyDefaultRequestOptions(ro)
        .load(productImage)
        .into(imgView); 

Similarly, request options can be added to Glide when you build custom Glide object using GlideBuilder.

Image Transitions

You can apply transitions to an image loading request by calling transition() method on RequestBuilder, the method takes TransitionOptions as argument. Transition will be shown while image is being downloaded. That is why Image transition will be applied to the image when it is being loaded from disk cache, locally from the device or remotely, but not when it is downloaded from memory cache.

In the example below, transition method is used with DrawableTransitionOptions. Other Glide provided TransitionOptions implementations are BitmapTransitionOptions, DrawableTransitionOptions, and GenericTransitionOptions.

 Glide.with(this) 
        .load(productImage).transition(DrawableTransitionOptions.withCrossFade())
        .into(imgView);
 

You can use android provided animations with Glide using GenericTransitionOptions.

 Glide.with(this)
.applyDefaultRequestOptions(ro)
.load(productImage).transition(GenericTransitionOptions
                         .with(android.R.anim.slide_in_left))
.into(imgView); 

You can define animation in xml use it with Glide.

 <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="-50%p" android:toXDelta="0"
        android:duration="@android:integer/config_mediumAnimTime"/>
    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
        android:duration="@android:integer/config_mediumAnimTime" />
    <rotate
        android:fromDegrees="0"
        android:toDegrees="180"
        android:pivotX="50%"
        android:pivotY="50%" />
</set>
 
 Glide.with(this)
        .applyDefaultRequestOptions(ro)
        .load(productImage).transition(GenericTransitionOptions
                                 .with(R.anim.image_glide_animation))
        .into(imgView);
 

You can define custom animation using ViewPropertyTransition.Animator and apply it to image loading request as shown below.

 ViewPropertyTransition.Animator rotationAnim = new ViewPropertyTransition.Animator() {
    @Override
    public void animate(View view) {
        ObjectAnimator rotation = ObjectAnimator.ofFloat(view, "rotation", 0f, 90f);
        rotation.setDuration(1000);
        rotation.start();
    }
};
Glide.with(this)
        .applyDefaultRequestOptions(ro)
        .load(productImage).transition(GenericTransitionOptions.with(rotationAnim))
        .into(imgView); 

Request Listeners

To listen to image loading request failure or success events, you need to add request listener to RequestBuilder object. To create a listener, you need to implement RequestListener and implement its methods, onLoadFailed and onResourceReady.

 RequestListener rl = new RequestListener<Drawable>() {
    @Override
    public boolean onLoadFailed(@Nullable GlideException e, Object model,
                                Target<Drawable> target,
                                boolean isFirstResource) {
     //do something like reporting failure
        return false;
    }

    @Override
    public boolean onResourceReady(Drawable resource, Object model,
                                   Target<Drawable> target,
                                   DataSource dataSource,
                                   boolean isFirstResource) {
        //do something
        return false;
    }
};

Glide.with(context)
        .load(fileUrl)
        .listener(rl)
        .into(holder.imageView);
 

Clearing Cache

To clear the memory cache, you need to call clearMemory() on Glide object.

Glide.get(context).clearMemory();

To clear disk cache, you need to call clearDiskCache() method on Glide object in background thread.

 new AsyncTask<Void, Void, Void> {
  @Override
  protected Void doInBackground(Void... params) {
    Glide.get(applicationContext).clearDiskCache();
    return null;
  }
} 

Images List Recycleview Glide Example

The example below gets list of Image URLs from Firestore database and displays the list of images in RecyclerView. In recycler view adapter, image item is downloaded using Glide so that all the features of glide that are discussed above make the scrolling of images list smooth.

Activity

import android.app.ActivityManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;

import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
import com.bumptech.glide.load.engine.cache.LruResourceCache;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.QuerySnapshot;

import java.util.ArrayList;
import java.util.List;

public class ImageListActivity extends AppCompatActivity {

    private static final String TAG = "ImageListActivity";
    private FirebaseFirestore firestoreDB;
    private RecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_images_layout);

        firestoreDB = FirebaseFirestore.getInstance();
        recyclerView = findViewById(R.id.images_lst);
        LinearLayoutManager recyclerLayoutManager =
                new LinearLayoutManager(this);
        recyclerView.setLayoutManager(recyclerLayoutManager);

        DividerItemDecoration dividerItemDecoration =
                new DividerItemDecoration(recyclerView.getContext(),
                        recyclerLayoutManager.getOrientation());
        recyclerView.addItemDecoration(dividerItemDecoration);

        //build glide object and set it as global singleton
        buildGlide();

        getImageUrlsFromFirestoreDb();
    }

    private void buildGlide(){
        GlideBuilder gb = new GlideBuilder();

        //set mem cache size to 8% of available memory
        LruResourceCache lruMemCache = new LruResourceCache(getMemCacheSize(8));
        gb.setMemoryCache(lruMemCache);

        //set disk cache 300 mb
        InternalCacheDiskCacheFactory diskCacheFactory =
                new InternalCacheDiskCacheFactory(this, 300);
        gb.setDiskCache(diskCacheFactory);

        //set BitmapPool with 1/10th of memory cache's size
        LruBitmapPool bitmapPool = new LruBitmapPool(getMemCacheSize(8)/10);
        gb.setBitmapPool(bitmapPool);

        //set custom Glide as global singleton
        Glide.init(this, gb);
    }
    private int getMemCacheSize(int percent){
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
        ((ActivityManager)
                getSystemService(ACTIVITY_SERVICE)).getMemoryInfo(mi);

        double availableMemory= mi.availMem;
        return (int)(percent*availableMemory/100);
    }

    private void getImageUrlsFromFirestoreDb() {
        firestoreDB.collection("images")
                .get()
                .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                    @Override
                    public void onComplete(@NonNull Task<QuerySnapshot> task) {
                        if (task.isSuccessful()) {
                            List<String> fileList = new ArrayList<String>();
                            Log.d(TAG, "number of images "+task.getResult().size());
                            for(DocumentSnapshot doc : task.getResult()){
                                fileList.add(doc.getString("url"));
                            }
                            ImageRecyclerViewAdapter imgRvAdapter = new
                                    ImageRecyclerViewAdapter(fileList,
                                    ImageListActivity.this);
                            recyclerView.setAdapter(imgRvAdapter);

                        } else {
                            Log.d(TAG, "Error getting image URLS", task.getException());
                        }
                    }
                });
    }
}

Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".ImageListActivity">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/images_lst"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"/>
</LinearLayout>
 

Recycler View Adapter

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.bumptech.glide.GenericTransitionOptions;
import com.bumptech.glide.Glide;

import java.util.List;

public class ImageRecyclerViewAdapter extends
        RecyclerView.Adapter<ImageRecyclerViewAdapter.ViewHolder> {

    private List<String> imageList;
    private Context context;

    public ImageRecyclerViewAdapter(List<String> list, Context ctx) {
        imageList = list;
        context = ctx;
    }
    @Override
    public int getItemCount() {
        return imageList.size();
    }

    @Override
    public ImageRecyclerViewAdapter.ViewHolder
                onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.image_item, parent, false);

        ImageRecyclerViewAdapter.ViewHolder viewHolder =
                new ImageRecyclerViewAdapter.ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(ImageRecyclerViewAdapter.ViewHolder holder, int position) {
        final int itemPos = position;
        final String fileUrl = imageList.get(position);

        Glide.with(context)
                .load(fileUrl).transition(GenericTransitionOptions
                .with(R.anim.image_glide_animation))
                .into(holder.imageView);
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public ImageView imageView;

        public ViewHolder(View view) {
            super(view);
            imageView = view.findViewById(R.id.image);
        }
    }
}