Android: onBindViewHolder can't cache images - android

I am trying to download images if they don't exist yet, and keep and re-use them if they've already been downloaded.
While downloading and displaying works, re-using them does not. It seems like mValues isn't the same mValues in different calls.
Here is my code
public void onBindViewHolder(final TabFragment1.SimpleItemRecyclerViewAdapter.ViewHolder holder, int position) {
holder.mItem = mValues.get(position);
holder.mIdView.setText(mValues.get(position).title);
holder.mContentView.setText(mValues.get(position).description);
if (holder.mThumbnail == null) {
System.out.println("thumbnail null"); //NEVER gets called
} else {
if(mValues.get(position).thumbnailData!=null){
System.out.println("thumbnailData contained"); //NEVER GETS CALLED, IT ALWAYS SEEMS TO BE NUL
holder.mThumbnail.setImageDrawable(mValues.get(position).thumbnailData);
} else {
System.out.println("thumbnailData downloading"); //always happening
try {
AsyncTask<String, Void, Bitmap> execute = new DownloadImageTask((ImageView) holder.mThumbnail)
.execute(holder.mItem.thumbnailUrl);
mValues.get(position).thumbnailData = holder.mThumbnail.getDrawable();
if(mValues.get(position).thumbnailData!=null) {
System.out.println("is null"); //hardly ever gets printed
} else {
System.out.println("is not null"); //always gets printed
}
} catch (Exception ex) {
}
}
}
}
SOLVED: my code was wrong. The thumbnail update didn't wait for the AsyncTask to finish and I messed up the if statement so it tricked me into thinking the "new" thumbnail worked. I put the thumbnail update into the AsyncTask and now it's working :)

Your if else statement, inside try catch has interchanged log statements...Here your if statement checks for (thumbnail !=null) but prints it as null if found true.... so my point here is, your thumbnail object is always null, as its value would depend upon completion of Asynctask you started just above it, hence you cannot access it just like that, because this downloading process will run on separate thread.

You could use Picasso library for android, that will cache images and other cool things for you.

You can use any good Image loading library like UIL or Picasso
or Glide.
You can also try setDrawingCacheEnabled(true) on your recycler view.

Related

Picasso is showing wrong image when file does not exists on local storage

I've a recycler view which shows list of users with their avatar. After login I download all avatar and save them locally with the file name which is user's row id from mongo db. When the avatar file is not available in storage Picasso loads one of other user's avatar at random. What should I do prevent this.
This is common code for showing or downloading image in case of unavailability
I tried using cache policies in Picasso but that didn't work either.
public ImageUtils loadFromDisk(String id, ImageView target) {
Log.d(TAG, "loadFromDisk: imageId: " + id);
File directory = contextWrapper.getDir("avatars", Context.MODE_PRIVATE);
File avatarPath = new File(directory, id);
if (avatarPath.exists()) {
Log.d(TAG, "loadFromDisk: loaded from disk" + avatarPath);
Picasso.get().load(avatarPath)
.memoryPolicy(MemoryPolicy.NO_CACHE)
.networkPolicy(NetworkPolicy.NO_CACHE)
.into(target);
} else {
Picasso.get().load(R.drawable.image_thumb).into(target);
}
return this;
}
I call loadFromDisk from a Singleton ImageUtils class in RecyclerView bindView holder class.
PS: bindViewHolder code
public void onBindViewHolder(#NonNull RecentChatsViewHolder recentChatsViewHolder, int i) {
Log.d(TAG, "onBindViewHolder: content row");
if (recentChat.isNew) {
recentChatsViewHolder.blueDot.setVisibility(View.VISIBLE);
} else {
recentChatsViewHolder.blueDot.setVisibility(View.GONE);
}
recentChatsViewHolder.avatar.setImageResource(R.drawable.ic_info_outline_white_48dp);
ImageUtils.getInstance(context).loadFromDisk(recentChat.id, recentChatsViewHolder.avatar); //this calls above function here I don't pass the else condition of above method so there's no race condition
}
Since this is inside a recyclerView, I am assuming there's some kind of race condition happening. When you call FileDownloader to download some image. It downloads the image and the loads it to the target. But the target is not correct since the recycler view is now scrolled.
Maybe, you could avoid this by adding id as a tag to the ImageView and check the tag once the image is downloaded. If the tag is different, don't load the image into the view. -> This would only work if the download callback/lambda passes the id.
EDIT:
I don't know what's happening with fallback. It would be better if you could remove it from the snippet you have added.
I can suggest 2 things.
ImageView.setImageBitmap(null) or ask Picasso to clear the target before loading a new image. If the avatar doesn't exist, clear the target.
Add errorDrawable so if there's an error, it won't show the previous image.

Parse Android SDK nested saveInBackground not working

I'm working on an existing Android App with parse back-end (localDatastore is enabled but not used in this context) which has the following object structure:
Object A has an array of objects B
Object B has an array of objects C
I save this object structure using saveInBackground in calling the next saveInBackground in the done SaveCallback in reverse Order(C,B,A). For the inner two that works fine, but the top level object isn't saved.
Here's the code (frame, newStep and order are objects of classes inheriting from the ParseObject class)
frame.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
Log.i("Info", "frame.save callback OK");
frames.add(frame);
newStep.setTimeFrames(frames);
newStep.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
Log.i("Info", "newStep.save callback OK");
List<ProcessingStep> steps = order.getProcessingSteps();
steps.add(newStep);
order.setProcessingSteps(steps);
order.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null){
Log.i("Info", "order.save callback OK");
}else{
Log.i("Info", "order.save callback FAIL");
}
}});
} else {
Log.i("Info", "newStep.save callback FAIL");
e.printStackTrace();
}
}
});
} else {
Log.i("Info", "frame.save callback FAIL");
e.printStackTrace();
}
}
});
In the console log I see only "frame.save callback OK", the "newStep.saveInBackground()" seems to be executed too (object appears in backend) however I never get the log message in the callback.
If I save all objects before synchronously without references to each other first and then call the code here, it seems to work (worked at least once) but took for ever (minutes). Queries from the back-end are super fast and the frame object is also saved almost instantly but the done-callbacks seem to bugging. When it fails I do not get any exception, log anything it just seems to fail silently.
I'm looking for any insight why Parse behaves like that as well as how to fix it.
edit: The problem seems to be with the double relation (A to B and B to C). If I try with only A to B or B to C it works just fine. What remains mysterious to me, however, is why splitting the operation up with callbacks doesn't seem to work.
The problem was the enabled localdatastore. Without localdatastore enabled everything works as it should.

How to cache images with Picasso async?

At start of my application I have list of imageLinks
List<String> imageLinks = Arrays.asList("http://example.com/1.png",
"http://example.com/2.png",
"http://example.com/3.png"
...
"http://example.com/n.png");
I want to download the images async and in next run of my app without internet connection, I want to display the images with Picasso:
mPicasso.load("http://example.com/1.png").into(imageView)
But when I'm trying to download the image in background (io) thread with rxJava. I want to download it in background (io) thread, because I need to display ProgressDialog while images are downloading and go to another activity when it will be done
for (String imageLink:imageLinks )
mPicasso.load(imageLink).into(new SimpleTarget()
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
Log.e(TAG, "onBitmapLoaded: "+imageLink);
subscriber.onNext(true);
subscriber.onCompleted();
})
the error occurs:
java.lang.IllegalStateException: Method call should happen from the main thread.
at com.squareup.picasso.Utils.checkMain(Utils.java:136)
at com.squareup.picasso.RequestCreator.into(RequestCreator.java:496)
My another idea is to download the images with Retrofit , but how to add downloaded image to Picasso disk cache to display it in next time?
I suppose you could use this
Bitmap bitmap = Picasso.with(this)
.load(productCoverImageURL)
.get();
Use that inside your RxJava async job
Just like the message from the exception you posted says - you need to make your Picasso request on the main thread.
Don't be afraid it won't do the actual image (down)loading on the thread you made a call from.
This is exactly why loading into Picasso's Targets is described as an "asynchronous" way of loading images. You used the word "async" in your very question but I'm afraid you don't fully understand yet what this means.
Found solution Picasso.fetch(Callback)
In my case:
mPicasso.load(imageLink).fetch(new Callback() {
#Override
public void onSuccess() {
Log.e(TAG, "onBitmapLoaded: " + imageLink);
subscriber.onNext(true);
subscriber.onCompleted();
}
#Override
public void onError() {
Log.e(TAG, "onBitmapFailed: " + imageLink);
subscriber.onNext(false);
subscriber.onCompleted();
}
});

Picasso errors on callback

I've noticed that occasionally images won't load in my app through picasso and that picasso is in fact erring. I am using two images per list item in a list view. Here's the picasso code:
Picasso.with(DashboardActivity.this).load(status).into(iv_customer_status_pic, new Callback() {
#Override public void onSuccess() {
Log.d("Debug", "Picasso Success");
}
#Override public void onError() {
Log.d("Debug", "Picasso Errored");
}
});
How can I ensure that the images are loaded, I don't want them to error and then make them disappear. Also why does it error? Is there a timeout? I noticed on more powerful devices it happens less.
The reasons why it fails might because of no Internet connection and Invalid Image URL.
With regard to the error handling refer to nPn's answer.
The reason the onError() callback for Picasso.with().load().into(target, callback) exists is because there is no 100% guarantee the load will be successful. For example if you are trying to load from a uri and you don't have an internet connection, the load will not be successful.
You can somehow attempt a re-try (which I think is already built into Picasso), but ultimately, you need to handle the case were the load fails (for whatever reason). One option would be to load a "default" image, like a generic "profile picture" if you were trying to load a specific users profile picture.
If you move the implementation of the callbacks to a separate class , or even the containing class you should be able to retry from the onError() call back. Here is what I am thinking:
class ContainingClass implements Callback.EmptyCallback
private int mRetryAttempts = 0;
#Override
public void onError() {
if (mRetryAttempts < 2) {
mRetryAttempts++;
// try again
} else {
mRetryAttempts = 0;
}
}
#Override
public void onSuccess() {
mRetryAttempts = 0;
}

Picasso image caching

I want to download the following image downloading code with Picasso image cache.
DownloadImage downloadImage = new DownloadImage();
downloadImage.execute(advert.getImgUrl());
private class DownloadImage extends AsyncTask<String, Void, Bitmap> {
#Override
protected Bitmap doInBackground(String... arg) {
Bitmap bmp = null;
try {
URL url = new URL(arg[0]);
bmp = BitmapFactory.decodeStream(url.openConnection()
.getInputStream());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
return null;
}
return bmp;
}
#Override
protected void onPostExecute(Bitmap result) {
if (result == null) {
Intent intent = new Intent(AdvertisingActivity.this,
AdvertisingErrorActivity.class);
intent.putExtra("ad-error", "Error downloading image");
}
adImg.setImageBitmap(result);
super.onPostExecute(result);
}
}
I have several questions regarding this.
I want to download more than one image in parallel. If I make repeated calls of Picasso.with(getActivity()).load(url); with different url values, does this get done?
I want to download images in one activity and use it in another activity. Is this possible? How can this be done?
If I call Picasso.with(getActivity()).load(url); more than once with the same url value, does this load the cached images for subsequent calls after the image has been downloaded?
If the image download process does not succeed for some reasons, can you make Picasso report you of the failure?
I've researched some more into your questions and decided that I should publish this as an answer rather than a comment.
Yes - Picasso loads images asynchronously so making repeated calls will cause images to be downloaded in parallel.
Yes - just make the call as normal and Picasso will handle the re-use of downloaded images e.g. in Activity1, call Picasso.with(this).load("image1"); and, later, make a call to the same URL in Activity2. The image will already be cached (either in memory or on device storage) and Picasso will re-use it, rather than downloading it again.
Yes - see above (Picasso will automatically use cached images where available)
This does not seem to have such a clear-cut answer. One thing you can do is provide an image to display if an error occurs while fetching the real image:
Picasso.with(context)
.load(url)
.placeholder(R.drawable.user_placeholder)
.error(R.drawable.user_placeholder_error)
.into(imageView);
The 'placeholder' will be displayed whilst the attempt is being made to fetch the image from the web; the 'error' image will be displayed, for instance, if the URL is not valid or if there is no Internet connection.
Update, 17/03/2014:
Picasso supports the use of a callback to report you of a failure. Modify your usual call (e.g. the above example) like so:
.into(imageView, new Callback() {
#Override
public void onSuccess() {
// TODO Auto-generated method stub
}
#Override
public void onError() {
// TODO Auto-generated method stub
}
});
In conclusion, it sounds like Picasso would be a great choice of library for you. It definitely makes image downloading very quick and very easy, so I like it a lot.

Categories

Resources