Erfan Mowlaei Erfan Mowlaei - 2 months ago 22
Android Question

LazyImageLoading into RecyclerView + saving LoadedImages to database

The problem is as it is stated in question title. In fact I want to load images which I have their url in my records into

RecyclerView
and at the same time persist downloaded image to database. I am using
realm.io
and
Glide
and my
RecyclerViewAdapter
is as below:

@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
final ProductModel obj = getData().get(position);
holder.data = obj;
holder.title.setText(obj.getTitle());
if (obj.getImage() == null) {

Glide
.with(context)
.load(obj.getImageUrl())
.fitCenter()
.placeholder(R.drawable.bronteel_logo)
.into(new GlideDrawableImageViewTarget(holder.icon) {
@Override
protected void setResource(GlideDrawable resource) {
// this.getView().setImageDrawable(resource); is about to be called
super.setResource(resource);
// here you can be sure it's already set
((ProductsFragment) mFragment).saveImage(obj, resource);
}
});

} else {
byte[] data = obj.getImage();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length, options);
holder.icon.setImageBitmap(bmp);
}
}

class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView title;
public ImageView icon;
public ProductModel data;

public MyViewHolder(View view) {
super(view);
title = (TextView) view.findViewById(R.id.textView);
icon = (ImageView) view.findViewById(R.id.imageView);

view.setOnClickListener(this);
}

@Override
public void onClick(View view) {
if (data.getImage() != null)
activity.startActivity(new Intent(activity, ProductActivity.class).putExtra("id", data.getId()));
}
}


And here's how I save images:

public void saveImage(final ProductModel data, Drawable drw) {
new AsyncImagePersister(data).execute(drw);
}

private class AsyncImagePersister extends AsyncTask<Drawable, Void, byte[]> {
private final ProductModel data;
AsyncImagePersister(ProductModel data) {
this.data = data;
}

@Override
protected byte[] doInBackground(Drawable... drawables) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
Bitmap bmp = drawableToBitmap(drawables[0]);
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
return stream.toByteArray();
}

@Override
protected void onPostExecute(final byte[] bytes) {
super.onPostExecute(bytes);
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
data.setImage(bytes);
}
});
}

public Bitmap drawableToBitmap (Drawable drawable) {
Bitmap bitmap = null;

if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
if(bitmapDrawable.getBitmap() != null) {
return bitmapDrawable.getBitmap();
}
}

if(drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
}

Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}


However, when loading the images for the first time from internet (using Glide) it shows wrong pictures for different places and on the other hand after it fetches all images, the saved images to realm are in their correct place.

So what am I doing wrong? Please help. Thanks.

Answer

The misplaced images is due to views are being recycled, So the loaded bitmap does not necessarily belong to the current position, And another thing to consider is that using AsyncTask inside a RecyclerView won't play nice and will cause lags in your UI, And for the final point, saving the byte[] array in your model might end up to a OOM exception!

If you want do some long running task inside your adapter, think of using a Service, IntentService or ThreadHandler, so you will be sending tasks one by one and the'd be queued and executed one by one.

About having offline access to images: One option could be using Glide.diskCacheStrategy method and use DiskCacheStrategy.ALL so the original image size will be cached and you can use later in offline mode

Second option is to use Picasso instead of Glide! so that you can use a custom RequestHandler and download the image and save it somewhere so you can access it later, consider memory management is all on your side and you should handle it!

here's a hint for your second option: create class which extends from RequestHandler:

CustomReqHandler : RequestHandler() {

Then you should override two methods: canHandleRequest(), load()

in canHandleRequest() you should determine whether you want to handle current request or not, so define a custom scheme for these requests and check if this is one of them like:

val scheme:String = data.uri.scheme

the 2nd method is load() which is executed on a background thread and returns a Result object, download the image, save it somewhere, and return Result object!