4

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.

Elletlar
  • 3,136
  • 7
  • 32
  • 38
Tarek
  • 146
  • 3
  • 15
  • Hiya. I added a lightly modified implementation below. If it is still giving you trouble, try the image URLs in the answer because it would be good to determine if it is a URL specific problem. Cheers. – Elletlar Jul 21 '18 at 00:20

2 Answers2

5
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'

Abdur Rehman
  • 1,247
  • 10
  • 13
4

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...

Elletlar
  • 3,136
  • 7
  • 32
  • 38
  • thank you for your replay and notices , but it already works for me if I set the bitmap parameter of onBitmapLoaded() in an ImageView inside this method, that is mean that bitmap is not null inside the inner class, but once it passed outside the inner class to an other variable like " private Bitmap posterBitmap " in my code , the posterBitmap value became null ! where I need it to be the actual value of the downloaded bitmap .. – Tarek Jul 21 '18 at 09:28
  • Ah okay. It works perfectly fine for me, I can pass the bitmap anywhere in my activity. But make sure you declare the Target as a member variable: "private Target mTarget" [Weak Reference Issue with Picasso](https://stackoverflow.com/questions/38617505/picasso-load-image-into-target). Other than that, there must be something else in the code setting it to null. – Elletlar Jul 21 '18 at 10:29
  • One other thing that might be worth checking, it isn't a confusion between synchronous and asynchronous? Because "private Bitmap posterBitmap" is still going to be null after: "Picasso.get().load(URL).into(mTarget)" is called. It may take several seconds for "private Bitmap posterBitmap" to be set in onBitmapLoaded from a separate thread. – Elletlar Jul 21 '18 at 10:34
  • "It may take several seconds for "private Bitmap posterBitmap" to be set in onBitmapLoaded from a separate thread" so how to solve it programmatically ? and I've declared the target Variable as you recommended but in Movie model class and still returns null , is that differ if I used this code in Activity or normal class ? – Tarek Jul 21 '18 at 12:52
  • It is difficult to comment without seeing the full activity. But have you put a breakpoint on onBitmapLoaded? I was concerned that you might be accessing the member variable 'posterBitmap' before onBitmapLoaded has been called to set it. But the solution is to start whatever tasks need the bitmap from onBitmapLoaded itself. So the code will end up looking a little disjointed because half of it is done before the load the other half afterwards. That's just the nature of a 'asynchronous', the code needs to be broken up into chunks. – Elletlar Jul 21 '18 at 13:05
  • Also, just to be clear onBitmapLoaded is called on the main thread. But Picasso loads the image in a separate thread then posts the results back to the main thread after a delay. I just confirmed this in the debugger - onBitmapLoaded does run on the main thread. – Elletlar Jul 21 '18 at 13:07
  • hey friend , it still not working with me, If you can check the code to test it , I will be appreciated for that. https://github.com/Tarekma91/PopularMoviesApp there is TODO in the adapter where I want to use this bitmap. – Tarek Jul 22 '18 at 00:27
  • I'm confused. The code that is there works fine. Why do you want to manage the bitmap manually instead of giving the ImageView to Picasso? "Picasso.get().load(url).into(holder.moviePosterIV);"? – Elletlar Jul 22 '18 at 10:49
  • because I need to get the poster again in DetailsActivity , and I don't want to repeat getting the image using picasso again , and I'm going to save the movies in db and want to save the image as blob to retrieve it directly from db and prevent downloading it each time user opens the app ... – Tarek Jul 22 '18 at 11:15
  • Picasso has a lot of caching options that will prevent images being re-downloaded: [How do I use disk caching in Picasso](https://stackoverflow.com/questions/23978828/how-do-i-use-disk-caching-in-picasso) – Elletlar Jul 22 '18 at 11:26
  • I tried it but didn't load any image ! may be because it's an old solution and there are new one ..! – Tarek Jul 22 '18 at 13:05
  • and in case this solution works ,I don't understand how to save it as blob in db , where is the variable which will be converted ?! – Tarek Jul 22 '18 at 13:12
  • I meant let Picasso cache internally so that you don't need to store the image or manage the size of the cache. You can just use Picasso again on the detail page and it should open quickly in most cases. – Elletlar Jul 22 '18 at 13:19
  • well , that is means that save image's url as text in db instead of bitmap ? right? but that will use a lot of internet data to reload each time ,, which I want to save ! – Tarek Jul 22 '18 at 13:22
  • Yes I would just store the URLs in the DB. I didn't check the code, but I would use 'Room' for the DB. I wrote part of an app like this for one of America's largest cable companies. We only stored the URLs for the movie images and used a Picasso-like library for handling the image loading and caching. – Elletlar Jul 22 '18 at 13:28
  • well , Thank you , I will use Room – Tarek Jul 22 '18 at 13:40
  • Also my 2 pence on this: I would just focus on creating a great app with the right features and quality. You can always tweak the image loading at the end, that may involve getting a more in depth understanding of the Picasso cache, or swapping Picasso out for Glide or writing a custom solution. But personally, I wouldn't spend much time on this until the end. It isn't really architectural problem or something that needs to be addressed now. – Elletlar Jul 22 '18 at 13:42
  • Yes Room is the way forward. You will see a lot of code on StackOverflow using SQLiteOpenHelper or using ContentProviders in conjunction with CursorLoaders, but Room is the 2018 way of doing it. – Elletlar Jul 22 '18 at 13:45