The Picasso library allows one to load an image easily like:
Picasso.with(context).load(url).into(imageview);
The API also allows to specify an error image. But what can I do if I want the library to first try three or four different URLs before giving up and displaying the error image? Ideally these images would be tried sequentially, falling back to the next one if the previous wasn't loaded.
Natively there's no API for such functionality. But with some clever coded Picasso.Target you can easily achieve such functionality.
I'll add here a quick hack-untested code that should give you a rought idea on what to look for. You'll have to test and maybe fine tune, but that should be pretty OK.
private static final List<MultiFallBackTarget> TARGETS = new ArrayList<MultiFallBackTarget>();
public static class MultiFallBackTarget implements Picasso.Target {
private WeakReference<ImageView> weakImage;
private List<String> fallbacks;
public MultiFallBackTarget(ImageView image){
weakImage = new WeakReference<>(image);
fallbacks = new ArrayList<String>();
TARGETS.add(this);
}
public void addFallback(String fallbackUrl){
fallbacks.add(fallbackUrl);
}
public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from){
removeSelf();
ImageView image = weakImage.get();
if(image == null) return;
image.setImageBitmap(bitmap);
}
public void onBitmapFailed(Drawable errorDrawable){
ImageView image = weakImage.get();
if(image == null) {
removeSelf();
return;
}
if(fallbacks.size() > 0){
String nextUrl = fallbacks.remove(0);
// here you call picasso again
Picasso.with(image.getContext()).load(nextUrl).into(this);
} else {
removeSelf();
}
}
public void onPrepareLoad(Drawable placeHolderDrawable){}
private void removeSelf(){
TARGETS.remove(this);
}
}
Remember that Picasso does not hold strong references to the Target you put inside into(object). That means, internally Picasso uses WeakReference to that.
So that means that you need that self reference in TARGETS to keep reference of all MultiFallBackTarget you create and allow them to self-remove when their job is done.
Related
I'm working on movies Android app which get list of movies from an API which provides a poster path for all movies.
I want to get the image as Bitmap from the image's URL to save it as a Bitmap variable in the model class. I want to save the image as blob in the DB to retrieve it directly without redownloading it each time the user opens the app. Is that possible?
I want to do something like this, but it always returns null.
private Bitmap posterBitmap;
public void setPosterBitmap () {
Picasso.get().load(POSTERS_URL).into(new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
posterBitmap = bitmap; // I don't want to set it to an Image view here
}
#Override
public void onBitmapFailed(Exception e, Drawable errorDrawable) {}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {}
});
}
Thanks in advance.
new Thread(() -> {
try {
Bitmap mBitmap = Picasso.get().load(link).get();
} catch (Exception e) {
Log.e(""+e);
}
}).start();
By using
implementation 'com.squareup.picasso:picasso:2.71828'
This code is working for me:
...
private static final String TAG = MainActivity.class.getName();
private Target mTarget;
...
mTarget = new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
if (bitmap == null) {
Log.w(TAG, "Null");
} else {
Log.i(TAG, "Worked");
}
}
#Override
public void onBitmapFailed(Exception e, Drawable errorDrawable) {
Log.w(TAG, "failed");
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
Log.i(TAG, "Prepare");
}
};
// Small image loads without resize
// Picasso.get().load("http://www.theretirementmanifesto.com/wp-content/uploads/2016/08/Yoda-free-clip-art-680x410.jpg").into(mTarget);
// Mega high res out of memory image
Picasso.get().load("https://upload.wikimedia.org/wikipedia/commons" +
"/5/5e/M104_ngc4594_sombrero_galaxy_hi-res.jpg").
resize(100, 100).into(mTarget);
Also I'm assuming that the following line is in the manifest:
<uses-permission android:name="android.permission.INTERNET" />
Using this version of Picasso:
implementation 'com.squareup.picasso:picasso:2.71828'
Also it may be worth declaring the Target as a member variable due to issues Picaso has arising from 'weak references'. You will have to research this, but I believe it may be unsafe to declare the Target as an anonymous inner class.
Also it may be necessary to call resize(x, y) to prevent an out of memory situation depending on the image sizes and whether you control their source.
UPDATE:
The project won't work as written because it is using an synchronous solution, but you're making an asynchronous call:
holder.moviePosterIV.setImageBitmap(movie.getPosterBitmap());
The code:
public Bitmap getPosterBitmap() {
target = new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
posterBitmap = bitmap;
}
#Override
public void onBitmapFailed(Exception e, Drawable errorDrawable) {}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {}
};
return posterBitmap;
}
It cannot be done like this. In a nut shell, the Target is going to be called in the future when the image has been downloaded. But the code is being written as if the image is ready immediately.
It is a synchronous solution to an asynchronous problem. In synchronous programming we write 1 line after the other then return the result when the data is ready.
If we wrote it synchronously:
f() {
image = getImage(url) // the program could be blocked here for minutes
return image
}
So instead we do it asynchronously:
f() {
getImageInTheFuture(url, imageReadyFunc); // call imageReadyFunc when it is finally downloaded
}
imageReadyFunc(image) {
setTheImage();
}
The asynchronous solution prevents the app from blocking, but it is also a real pain because we can no longer use a 'return' statement. Instead we have to break the code up into 2 sections. Code that we can run before the image is available. Code that we can run after the image is available.
But under the hood Picasso is doing all of this work for you. I'd really advise against trying to manage the bitmaps directly. In the bad old days of Android before Picasso, Glide, etc. apps used to routinely crash and run out of memory trying to manage their own bitmaps. It is technically difficult to do without causing memory leaks and running out of memory.
Hope that makes sense...
I have come across a very interisting issue. I am using the bellow code to load bitmaps using picasso:
final Target target = new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
// loaded bitmap is here (bitmap)
Log.i(TAG, "bitmapLoaded");
imageView.setImageBitmap(bitmap);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
Log.i(TAG, "bitmapFailed");
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
};
imageView.setTag(target);
Picasso.with(this)
.load(photoUrl)
.into(target);
I know a lot of questions have being asked about picasso not loading images due to weak reference but I don't think that's the case, since I have followed the solutions suggested in many topics to reference target like above.
In my program, I use this same code in 3 different classes and in 3 distinct moments. What I have noticed is that whenever I call this method for the first time it doesn't work, but for the next times it works, doesn't matter which of the 3 calls is being used. I can say that because I print different messages to the log from this 3 different methods.
Any thoughts about what is going on or have I missed something?
Thank you in advance.
Try to achieve this with using async approach.
Picasso.with(context).load(URL).into(profile, new Callback() {
#Override
public void onSuccess() {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {//Use your "bitmap" here
Bitmap innerBitmap = ((BitmapDrawable) profile.getDrawable()).getBitmap();
}
}, 100);
}
Also you may try to use Glide https://github.com/bumptech/glide
Problem: The problem is Picasso holds a weak reference to the target class and it got GARBAGE COLLECTED.
Solution: Convert it to class field instead of using it as local reference.
I think I kind of reinvent caching in Java but have a point I don't get further.
In case the answer is anywhere on Stackoverflow for this issue I might had not understood it when searching or didn't understand the required complexity and searched for a more easy way.
Short what I want to do: call a method on an Object. The object should load a picture and store it as Image. Then it should decorate itself with an Decorator so that called method will next time only return the image with no more IO operations.
My Interace Picture Interafce is simple like this:
import java.awt.*;
public interface PictureInterface {
public Image getImage();
}
My Decorator looks like this:
import java.awt.*;
public class PictureDecorator implements PictureInterface {
private final Picture p;
public PictureDecorator(Picture p){
this.p = p;
}
public Image getImage(){
return this.p.pipeImage();
}
}
It saves a Picture and on getImage() calls pictures pipeImage - the picture "real" getImage().
And last but not least the Picture Class:
import java.awt.Image;
public class Picture implements PictureInterface{
private final String path;
private final Image image;
public Picture(String path){
this.path = path;
}
private void loadImage(){
this.image = /*IO Magic Loading the Image from path*/
}
public Image getImage() {
loadImage();
/*Decorate Yourself with Picture Decorator*/
return /*Decorator.getImage*/;
}
Image pipeImage(){
return this.image;
}
}
If getImage is called I want Picture to Decorate itself and call the Decorators getImage and most importent overwrite its old refference (Java is call by value, this is where i'm stuck atm) so on further getImage Calls the Decorators getImage Method is called.
As a little extra-question I think my access to the mage from Decorator is not best practice, hints welcome ^^
EDIT:
To add a thing: I allready thought if this it not possible: what would be "smarter": go for if(image==NUll) or make a decorateYourself() function where image is loaded and decorator returned in Picture and in Decorator it only returns itself, apply this to the Image var and then call getImage, like:
ImageInterface x = new Image("path);
x = x.decorateYourself()
Image i = x.getImage()
this ways i would only do a method-call to return the decorator itself, but i have to call both methods ...
If getImage is called i want Picture to Decorate itself and call the
Decorators getImage and most importent overwrite its old refference
(Java is call by value, this is where i'm stuck atm) so on further
getImage Calls the Decorators getImage Method is called.
A decorator doesn't work in this way.
With decorator you want to augment or diminish a behavior of an existing class without being invasive for this class : no needed modification.
So the decorator instance decorates an object that has to share with the decorator class a common type and a common method.
Besides I don't think that you need to use a decorator.
Here you don't decorate a picture but you bypass its loading if it was already previously performed.
I think that it would be more suitable to use a proxy that decides whether it must load the resources of get it from the cache.
Don't worry, it doesn't change many things in the classes you have introduced: interface, common method and object wrapping are still required.
In your case PictureInterface is the common type between the proxy class and the proxy subjects classes that provides the common method : getImage().
import java.awt.*;
public interface PictureInterface {
public Image getImage();
}
PictureProxy, a proxy class could implement PictureInterface to act as any PictureInterface instances.
PictureProxy should be responsible to check if it has cached the result of a previous loading of the image. It is the case it returns it. Otherwise it calls getImage() on the Picture instance that holds and it caches the result.
import java.awt.*;
public class PictureProxy implements PictureInterface {
private final Picture p;
private final Image image;
public PictureProxy(Picture p){
this.p = p;
}
public Image getImage(){
if (image != null){
return image;
}
image = p.getImage();
return image;
}
}
And Picture class should not be aware of the proxy when it performs getImage().
It is the proxy class that handles the state of the cache.
import java.awt.Image;
public class Picture implements PictureInterface{
private final String path;
private final Image image;
public Picture(String path){
this.path = path;
}
private void loadImage(){
this.image = /*IO Magic Loading the Image from path*/
}
public Image getImage() {
loadImage();
return image;
}
}
From the client of the classes you could do something like that :
Picture picture = new PictureProxy(new Picture("picturePath"));
Image img = picture.getImage(); // load the image from Picture the first time and get it
Image img = picture.getImage(); // get it from the PictureProxy cache
The code below shows my Assets class which allows me to load the texture once and call it when I need it globally. If you see that my pack is called house1.pack meaning that there will be more than one house with different art styles. I was wondering if I could achieve a design similar to the popular app Clash Royale with my current code and minor tweaks. I want there to load a different .pack file based on player rank when they are in game. The different houses have the same objects with the same png file names and sizes, but they are just drawn differently.
Thanks,
Denfeet
public class Assets implements Disposable, AssetErrorListener {
public static final String TAG = Assets.class.getName();
public static final Assets instance = new Assets();
private AssetManager assetManager;
public AssetFonts fonts;
public AssetDoor door;
public AssetPlatform platform;
public AssetPlayer player;
public AssetControls controls;
// singleton
private Assets() {
}
public void init(AssetManager assetManager) {
this.assetManager = assetManager;
assetManager.setErrorListener(this);
assetManager.load("TexturePacker/house1.pack", TextureAtlas.class);
assetManager.finishLoading();
TextureAtlas atlas = assetManager.get("TexturePacker/house1.pack");
//create game resource objects
fonts = new AssetFonts();
door = new AssetDoor(atlas);
platform = new AssetPlatform(atlas);
player = new AssetPlayer(atlas);
controls = new AssetControls(atlas);
}
#Override
public void error(AssetDescriptor asset, Throwable throwable) {
Gdx.app.error(TAG, "Couldn't load asset '" + asset.fileName + "'", (Exception) throwable);
}
public class AssetFonts {
...
}
public class AssetPlayer {
...
}
public class AssetControls {
...
}
public class AssetDoor {
...
}
public class AssetPlatform {
...
}
}
You can load and unload assets at any time. Provided you don't want to use house1.pack anywhere else in your game, you can:
assetManager.unload("TexturePacker/house1.pack")
to dispose of your house1.pack, then do most of what you have in your init() method to again
assetManager.load("TexturePacker/house2.pack", TextureAtlas.class);
assetManager.finishLoading();
//etc
Not sure how you'd structure your code, but the above is the idea. Depending on how many assets you're talking about (loading time), you may need a natural transition point (not necessarily a full loading screen). But if you want to change textures from one house to another on the fly, you'd probably want to load them all at once at the beginning and find a different method to swap them.
I have a List<Presenter> presenterList;
With
public class Presenter(){
String name;
String imageRef; // Filename to be downloaded
Bitmap image;
(etc...)
}
I'm working with AsyncTask & once the image has downloaded, I wish to go through the list and set Image value to the newly download image.
so far i have
Presenter pres = PresenterList.get(Position);
pres.Image = new (Bitmap) downloadedImageFromImageRef;
however i fear that this will not relate to the Image value of the presenter within the list.
How do i refer, or even assign to the specific Presenter attribute within the list?
From working with C (many years ago), i belive somthing like a pointer to the value in which to assign .Image would work
Thank you in advace
Well, if you have C experience, then the thing to know about Java is that it doesn't use pointers, but it does use references. So if I'm understanding your problem correctly, you are already using the Image attribute of a Presenter instance elsewhere and then you want to fill it in later. Assigning pres.Image = new (Bitmap) DownloadedImageFromImageRef; would not work in this case because other objects are looking at a different Bitmap object reference.
What you might need to do is use an observer pattern -- it depends on the details of your problem. Here's an example:
Somewhere in the code I have a class Foo that wants to use the Image property from a Presenter instance. But, since that property isn't set until later, this class wants to be notified when it is ready (it is an observer).
public class Presenter {
String Name;
String ImageRef; // Filename to be downloaded
private Bitmap Image;
private PresenterImageObserver observer;
public void setImageObeserver(PresenterImageObserver pio) {
this.observer = pio;
}
public void setImage(Bitmap b) {
this.Image = b;
this.observer.imageLoaded(b);
}
}
public interface PresenterImageObserver {
public void imageLoaded(Bitmap b);
}
public class Foo implements PresenterImageObserver {
//Foo's constructor. It wants the image from presenter p, when it is ready
public Foo(Presenter p) {
p.setImageObserver(this);
}
public void imageLoaded(Bitmap b) {
//b contains the loaded image and this Foo instance can use it now!
}
}
You'd need to set the image using pres.setImage(new (Bitmap) downloadedImageFromImageRef);.
So you have to find in your list the Presenter for which the correct imageref. You have basically two options.
First, you simply iterate through your list
for (Presenter presenter: presenterList) {
if (presenter.imageref.equals(imageName) {
Presenter.image = new Bitmap(downloadedImage);
break; // found : stop iterations
}
}
Secondly, you can create a HashMap for your presenters, with the imageref as the key :
HashMap<String, Presenter> map = new HashMap<>();
for (Presenter presenter: presenterList) {
map.put(presenter.imageref, presenter);
}
Then, you can directly find the right presenter through map.get(imageName)