Picasso Image Caching with Recyclerview - java

I am using picasso inside my recyclerview adapter for loading images from network. I've tested it with 40+ items in my recyclerview and 29 of them have images.
I noticed that upon reaching the end of list, when I scroll back up, some of the images are being reloaded from network instead of cache. Is there any way to prevent reloading the image again from the network? Or perhaps an alternative for reducing network bandwidth usage.
Picasso.with(context)
.load(HOST + post.getImage_url())
.resize(1920, 1080)
.onlyScaleDown()
.networkPolicy(NetworkPolicy.OFFLINE)
.into(holder.ivPostImage, new Callback() {
#Override
public void onSuccess() {
}
#Override
public void onError() {
Picasso.with(context)
.load(HOST + post.getImage_url())
.resize(1920, 1080)
.onlyScaleDown()
.into(holder.ivPostImage);
}
});
Above is the code that I use for caching images loaded from the network.

Related

How to use transition animations in API 16?

I wrote the following code:
Glide.with(this).asBitmap().load(userImageURL).into(new CustomViewTarget<ImageView, Bitmap>(userAvatarImage) {
#Override
protected void onResourceCleared(#Nullable Drawable placeholder) {
progressBarUserAvatar.setVisibility(View.GONE);
userAvatarImage.setImageBitmap(null);
}
#Override
public void onLoadFailed(#Nullable Drawable errorDrawable) {
progressBarUserAvatar.setVisibility(View.GONE);
final Bitmap circleLetterTile = tileProvider.getCircularLetterTile(userFullName);
userAvatarImage.setImageBitmap(circleLetterTile);
}
#Override
public void onResourceReady(#NonNull Bitmap resource, #Nullable Transition<? super Bitmap> transition) {
progressBarUserAvatar.setVisibility(View.GONE);
final Bitmap circleAvatarBitmap = tileProvider.getCircularImage(resource);
userAvatarImage.setImageBitmap(circleAvatarBitmap);
userAvatarImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent showProfilePictureIntent = new Intent(UserProfileActivity.this, DisplayUserImageActivity.class);
showProfilePictureIntent.putExtra("userImageURL",userImageURL);
final String transitionName = ViewCompat.getTransitionName(userAvatarImage);
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
UserProfileActivity.this, userAvatarImage, transitionName);
UserProfileActivity.this.startActivity(showProfilePictureIntent, options.toBundle());
UserProfileActivity.this.overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
}
});
}
});
It basically displays the user's image using Glide. It also set a onClick listener that opens the image in large mode with black background. Currently, UserProfileActivity is empty (just loads the layout). The problem is that I can't use getTransitionName because it's possible only for api 21. I'm have to use API 16 in my project so it's a problem. What would be the easiest way to achieve it? I looked into previous topics but they all between 2013-2015 so I thought maybe there is a way to do it.
This is not possible to do, using API >21. As you can refer to the official documentation here https://developer.android.com/reference/android/app/ActivityOptions#makeSceneTransitionAnimation(android.app.Activity,%20android.view.View,%20java.lang.String).
I'm not sure, why you don't have warning usage, but do for getTransitionName. Unfortunately it's not supported on early version. As another reference you can check https://www.programcreek.com/java-api-examples/?class=android.app.ActivityOptions&method=makeSceneTransitionAnimation about examples how to use transition animation.
Off topic
You can achieve animation you want by writing own tools with transition animation. It's not so easy, but it's pure view implementation, and you don't need new Android version. For the same reason, you can use Flutter (min version for which is Android 16) and make transition animation (hero animation) with few lines of codes. Because it's out of box implementation.

How to handle high resolution image?

I have a profile page and I also have profil image that is to be downloaded from server and then put in image view. Every time I open profil page this process repeats itself. When image is below 500 kb or around, everything is okay but when I put 6mb image on it, the page is not smoothly working. I mean when scrolling or doing anything everything takes time. It is like running high graphic game on weak computer. After undergoing this process, I tried to compress image and was able to reduce the size to 2 MB by this library https://github.com/zetbaitsu/Compressor. Nothing less. Why does this problem occurs.
I am doing this upload process on Async task.
Upload method
public void upload() {
if (filePath != null) {
Bitmap compressedImage = null;
try {
compressedImage = new Compressor(context).compressToBitmap(new File(filePath.getPath()));
} catch (IOException e) {
e.printStackTrace();
try {
compressedImage = MediaStore.Images.Media.getBitmap(context.getContentResolver(), filePath);
} catch (IOException e1) {
e1.printStackTrace();
}
}
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
compressedImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
final String path = MediaStore.Images.Media.insertImage(context.getContentResolver(), compressedImage, "Title", null);
StorageReference sRef = storageReference.child("images/" + name);
sRef.putFile(Uri.parse(path))
.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
#Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
if (ımageView != null)
ımageView.setImageURI(Uri.parse(filePath.getPath()));
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
Log.i("resultUpload", exception.getLocalizedMessage());
}
})
.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
#Override
public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
}
});
} else {
}
}
My download image method
public void download(final ImageView ımageView) {
StorageReference sRef = storageReference.child("images/" + name);
sRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
Picasso.get().load(uri).into(ımageView, new Callback() {
#Override
public void onSuccess() {
}
#Override
public void onError(Exception e) {
}
});
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.i("resultDownload", e.getLocalizedMessage());
}
});
}
What you can do, as Milan said, is to get the image from a URL online and then set that image to your imageView. First, you will have to make sure that you have internet permission with :
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
Then you can do the following to set an image into the imageView:
Bitmap bitmap = BitmapFactory.decodeStream((InputStream)new URL("Your image URL comes here".getContent());
imageView.setImageBitmap(bitmap);
This should work by getting a bitmap of the image located at the URL that you have specified. It will then set the imageView to that bitmap.
OR, what you can do is use Picasso. Add this to your app/build.gradle:
implementation 'com.squareup.picasso:picasso:2.5.2'
Then use this Java code to set the image into the imageView:
String imageUri = "Your image URL";
Picasso.with(getApplicationContext()).load(imageUri).into(imageView);
This uses Picasso to get the image from the URL into the imageView.
I hoped this helped.
This is not a Java problem. Irrespective of language or platform you need to do certain things to properly display an image on the web. So, instead of recommending any Java library (I'm sure you'll get many open source libraries in every language), I'm going to enumerate the basic steps you need to follow:
You need to resize (not cropping) the image for multiple devices. No decent website downloads 6MB image. Never! Not even 2MB image. My Facebook profile image, which I captured on my 8MP mobile camera, is 160 KB in size after uploading. Try to bring down the image size to around 100 - 150 KB. These are some usual image dimensions.
You'd create resized image for mobile, tablet and laptop. For mobile, it's usually 640 x 480. For tablet, it's usually 800 x 600. For Laptop, it's usually, 1024 x 768
Then compress the image as you did. Make sure that the final image is progressive jpeg/jpg. Progressive jpeg is compressed in layers. The browsers that support progressive images display a hazy preview when the image is being downloaded. This improves user experience. For the browsers that do not support progressive image, some work-arounds are used. For e.g. you can create a low quality version of your image (approx. 1KB in size) with opacity: 1; filter: blur(20px);. Thus a blurred version of the image will be displayed as the higher quality image is being downloaded. Initially you set the opacity of the higher quality image to 0. Once the higher quality image download is complete, you set the opacity of the lower-quality image to 0 and the higher-quality image to 1. You can test whether your image is progressive or not here and can create compressed progressive image here. You'd probably do these things programmatically, but these resources may serve as a reference.
Ensure that your server supports HTTP/2 which is fully multiplexed. You can see the multiplexing here. Thus the resources can get downloaded parallely.
Ensure your server supports gzip or compression. Thus the resources are transferred over the network in gzipped format and extracted automatically by the browser. This ensures fast download.
Use proper cache-control headers, so that the browser caches the images eliminating the need to download the image for every visit.
If possible a mobile first design should be used, so that for mobile the performance gets better.

Mapbox, MapSnapshotter.callback is not always invoked

I'm mainly using mapbox in my projet, but in one instance, I need to display the map in the recyclerView. To do so, I thought of using MapSnapshotter instead of the static Map Api since the user may not have connection.
Unfortunately, when doing my testing, I can't get the MapSnapshotter.callback working properly. Sometime the image is loaded/created and other time it's not, and it does feel to be random.
Mapbox.getInstance(this, MyMapbox.getToken());
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(new OnMapReadyCallback() {
#Override
public void onMapReady(MapboxMap mapboxMap) {
map = mapboxMap;
MapSnapshotter.Options options = new MapSnapshotter.Options(mapView.getMeasuredWidth(),mapView.getMeasuredHeight());
options.withRegion(mapboxMap.getProjection().getVisibleRegion().latLngBounds);
options.withStyle(mapboxMap.getStyleUrl());
MapSnapshotter mapSnapshotter = new MapSnapshotter(getContext(), options);
mapSnapshotter.start(new MapSnapshotter.SnapshotReadyCallback() {
#Override
public void onSnapshotReady(MapSnapshot snapshot) {
Log.i(LOG_TAG, "onSnapshotReady");
Bitmap bitmap = snapshot.getBitmap();
imageview.setImageBitmap(bitmap);
}
}, new MapSnapshotter.ErrorHandler() {
#Override
public void onError(String error) {
Log.i(LOG_TAG, error);
}
});
}
});
So after some twisting, I finally figure it out. The issue, was that MapSnapshotter.start is a Async task and since the phone is loading 3 items in the recycler view when first launched and in turn, each one of the item is calling the MapSnapshotter.start in the same thread before the previous one is done, so canceling it. This explain why only the last item has is image loaded.
A way to solve this issue is to make the Async task become Sync, but I don't recommande this solution.
The others way is add a property MapSnapshotter in your Adapter. Doing so, each of yours items will have is own MapSnapshotter.

Using Async task for gridview image loading

Ive been searching for a solution for hours now, hoping someone can help?
Ive got a swipe tab pages ui and each page has a gridview that loads images, works as expected but its very slow, even on a high end device. Can i set image resources using Async task? My adapter for my gridview is below:
public class PcAdapter extends BaseAdapter {
private Context context;
private Integer[] imageIds = {
R.drawable.pcserioussam, R.drawable.pc_trinetwo,
R.drawable.pc_leftfordead, R.drawable.pc_dungeondefenders,
R.drawable.pc_portaltwo, R.drawable.pc_spaz,
R.drawable.pc_laracroftattoo, R.drawable.pc_goatsim,
R.drawable.pc_deadblock
};
public PcAdapter(Context c) {
context = c;
}
public int getCount() {
return imageIds.length;
}
public Object getItem(int position) {
return imageIds[position];
}
public long getItemId(int position) {
return 0;
}
public View getView(int position, View view, ViewGroup parent) {
ImageView iview;
if (view == null) {
iview = new ImageView(context);
iview.setLayoutParams(new GridView.LayoutParams(230,300));
// iview.setScaleType(ImageView.ScaleType.FIT_CENTER);
iview.setPadding(5, 5, 5, 5);
} else {
iview = (ImageView) view;
}
iview.setImageResource(imageIds[position]);
return iview;
}
}
#mmlooloo's answer is totally relevant and I totally agree with him.
In complement, and to give a pist of solution, I would suggest you to use the library Picasso which is really easy to use and really powerful.
In your Adapter you can load your Images into the ImageView like this:
// Trigger the download of the URL asynchronously into the image view.
Picasso.with(context)
.load(url) // url of your image
.placeholder(R.drawable.placeholder) // drawable to display while downloading the image
.error(R.drawable.error) // drawable in case of failure
.into(imageView); // ImageView where you want to load the picture.
Picasso will take care of the caching and memory issues for you.
To learn more about Picasso and start using it, take a look here.
Your problem arises because of these several thing:
you are using async task with asyncTask.execute that is run one async task at a time so I recommend you this link:
Running multiple AsyncTasks at the same time -- not possible?
you are using viewpager and if your gridview is inside fragment viewpager when loads your current tab page also loads two tabs beside it to make user experience well when clicking other tabs and not wait to view inflated and so on ... so you must call async task inside tab select not inside page select and cancel it on onTabOnselected (you can not make viewpager to set.limitOffScreenPage(0); that wont work and the 0 is ignored. I highly recommend you use libraries to download images because this task requires lot of tips and tricks to have a smooth user experience and by itself it is an another project(do not reinvent wheel, if others invents for you)
you are decoding images on UI thread and decoding one at a time
your internet connection is slow
your image sizes are large
hope help you !

No redraw of ImageView when calling setImageBitmap inside of a Picasso Target

I have a big problem with loading images from cache on the getView of my Listadapter.
For image-loading we are using Picasso, and there I build a construct for loading images from cache and if there are no available images, THEN the images should be load from an online-URI.
(Here is a method, which is called inside of my getView of my Adapter)
#Override
protected void loadImages(final VehicleResultListItem inCurrentItem) {
// Try to load async the images from local path
Picasso.with(mContext)
.load(new File(mContext.getExternalFilesDir(null),
inCurrentItem.getVehicleId()))
.placeholder(inCurrentItem.getServiceType().getPlaceholderId())
.noFade()
.into(new Target() {
#Override
public void onBitmapLoaded(final Bitmap bitmap,
final Picasso.LoadedFrom from) {
mThumbnailImgView.setImageBitmap(bitmap);
}
#Override
public void onBitmapFailed(final Drawable errorDrawable) {
// If the local-path-loading fails, try to grab the images online.
Picasso.with(mContext)
.load(inCurrentItem.getImageURIs().get(0).getImageUriSizeM())
.placeholder(inCurrentItem.getServiceType().getPlaceholderId())
.into(new Target() {
#Override
public void onBitmapLoaded(final Bitmap bitmap,
final Picasso.LoadedFrom from) {
// On succesfully online-loading of the images, store and show them
mThumbnailImgView.setImageBitmap(bitmap);
saveToInternalSorage(bitmap, inCurrentItem);
}
#Override
public void onBitmapFailed(final Drawable errorDrawable) {
// NO interaction
}
#Override
public void onPrepareLoad(final Drawable placeHolderDrawable) {
// No interaction
}
});
}
#Override
public void onPrepareLoad(final Drawable placeHolderDrawable) {
mThumbnailImgView.setImageDrawable(placeHolderDrawable);
}
});
}
The problem is, that the image is loading correctly, but the imageView is not updating after image is loaded. In the OnImageLoaded, I am not in the UI-Thread, so ofc it does not update the view. So i tried anything:
Runnable
AsyncTask
Androids solution from (http://developer.android.com/training/displaying-bitmaps/process-bitmap.html#concurrency)
It looks like the Threads are getting confused or something like that.
Maybe there is a problem in calling Picasso inside of a Picasso-Target?
Do u have any suggestion?
Greetz and thx
Target must not be an anonymous class. Picasso keeps a WeakReference on Target that will most likely get gc'ed by the time the request completes.
Javadoc clearly states that.
Make your View implement Target instead and use that as the target or keep a reference to your target instances.
Also Picasso handles disk caching for you automatically, unless you've shipped the assets with your app, I would recommend you simply download them and then let Picasso manage the disk/memory cache for you.

Categories