14 minutes read

The vast majority of apps use images from external sources, as opposing to loading images from app bundles. You find it everywhere these days — social networking chats, news feeds, and so on. Fortunately, there's no need to reinvent the wheel. Several dynamic image loading libraries are readily available, such as Picasso, Glide, Fresco, and COIL. In this topic, you will learn about Picasso. Its main advantage over Glide is that it's a much smaller library, making it very lightweight. Bear in mind that each library has pros and cons, though. For example, others allow you to work with Gifs, but Picasso doesn't.

Adding the library

To connect the library, we need to insert the following dependency:

implementation 'com.squareup.picasso:picasso:2.71828'

Before doing this, it's best to check the latest version on the official GitHub page.

If you intend to download images from the internet, add corresponding permission to the AndroidManifest.xml file

<uses-permission android:name="android.permission.INTERNET" />

Note that this is only required if you intend to download images from the internet. It's not necessary if you will only be accessing images locally from the device.

First look

First, let's try to load a photo from internet into the ImageView element - to do this, we will use the following code in onCreate method:

val logoUrl = "https://resources.jetbrains.com/storage/products/intellij-idea/img/meta/intellij-idea_logo_300x300.png"

Picasso.get()
    .load(logoUrl)
    .into(imageView)

We get an object of the Picasso class with the static get() method and specify additional parameters. Specifically:

  • load() takes the image path. This can be a file on the device, a drawable resource ID, or a link to a remote file.

  • into() takes the element that the uploaded photo will be inserted into.

Let's add a drawable .png file and attempt to load it:

Picasso.get()
    .load(R.drawable.idea)
    .into(imageView)

The ImageView's height and width are set to "match_parent".

Loading the image via a URL or a drawable resource ID will give the same result:

Itellij Idea logo has the same width as screen

Scaling and cropping

We can change the dimensions of the image with the resize(targetWidth: Int, targetHeight: Int) method, but its aspect ratio won't be preserved automatically.

Let's call .resize(300, 200) after load() in Picasso methods chain to see the result:

Picasso.get()
    .load(R.drawable.idea)
    .resize(300, 200)
    .into(imageView)

Itellij Idea resized logo

As shown, if resizing the image changes its original proportions, it becomes stretched out and doesn't look natural. But what if we need to keep the proportions and shrink the image at the same time?

To preserve the aspect ratio, we need to use the centerCrop() or centerInside() scale function along with resize().

Attempting to use centerCrop() or centerInside() without first calling resize() will result in the following error:

java.lang.IllegalStateException: Center crop requires calling resize with positive width and height

Let's set the ImageView attributes to the following values:

<ImageView
    android:background="@color/teal_700"
    android:id="@+id/image"
    android:layout_width="400dp"
    android:layout_height="500dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

centerCrop() adjusts the picture so that it fills the specified bounds and then crops the excess. This means that the image might not be visible in its entirety:

.resize(400, 200)
.centerCrop()

Itellij Idea logo resized and cropped

centerInside() alters the image so that both dimensions are equal to or smaller than the specified bounds. This will make the image fully visible but may leave some free space. In spite of the resize, the proportions will remain the same, but the quality of the image can change.

.resize(400, 200)
.centerInside()

Itellij Idea logo inside resized area

There's also the fit() function, which must be applied without resize() because it has a resize function built-in.

fit() delays resizing until the dimensions of the ImageView are known — it's used to reduce the size of the image to fit the size of the ImageView.

Let's change the size of the ImageView to demonstrate how this works:

android:layout_width="400dp"
android:layout_height="500dp"

Using the .fit() option alone produces the following effect:

Intellij Idea logo fitted to the resized area

But the combination of .fit().centerCrop() gives a different result:

Itellij Idea logo fitted and cropped to the resized area

And, if we use the combination .fit().centerInside(), we get:

Itellij Idea logo fitted inside resized area

.fit() is normally used in combination with .centerCrop() or .centerInside(). Which scale function you choose depends on how you want the bounds of the picture to fit with the bounds of the ImageView.

Transforming

rotate() makes it easy to alter the angle of our picture. For example, we can use the below value to rotate the logo by 90 degrees when it's loaded.

.rotate(90.0f)

But what if we want to manipulate our image in more complex ways? Such as making it blurry?

We can do this with the transform() method. transform() takes any class that implements Transformation. The class must have a transform method that accepts a bitmap and returns the modified version.

However, we want to keep things simple and stick to the topic of downloading photos. So we'll use a library called Picasso Transformations to complete the task:

implementation 'jp.wasabeef:picasso-transformations:2.4.0'

Now we can use this library's blur transformation:

Picasso.get()
    .load(logoUrl)
    .transform(BlurTransformation(this))
    .into(imageView)

Itellij Idea logo blurred

Picasso Transformations also provides options such as GrayscaleTransformation and RoundedCornersTransformation. You can find out more on this GitHub page.

Priority

If we have lots of pictures, they won't all be downloaded at once. So what happens if we need to prioritize the images to ensure they are downloaded in the correct order?

We can use the priority() method for this purpose. It takes one of the Priority enum values: LOW, NORMAL, or HIGH.

For example, we could apply priority(Picasso.Priority.HIGH) to image.

Placeholder

placeholder() lets us tell Picasso which image we want to be displayed while waiting for images to be downloaded from the internet.

We can also use error() to specify the picture that should be shown if loading fails. In this situation, the placeholder image will be replaced with the error image. The Drawable resources in the example were added separately from the outside.

Placeholder appeared instead of logo

Picasso.get()
    .load(logoUrl)
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)
    .into(imageView)

If we disconnect the internet, we will see the placeholder picture for a moment. Then the error image will be displayed, once Picasso realizes that connecting to the resource has been unsuccessful.

Callback

We can also pass a callback function to into() and declare two methods — onError() to use if an error occurs and onSuccess() for when a photo downloads successfully.

For example, onSuccess() could be used to make a ProgressBar invisible once an image has been downloaded. And onError() could return to the previous screen or make the ImageView invisible if an image fails to download.

A simple example of how the callback function can be utilized is shown below. We need to import Callback from com.squareup.picasso package.

Picasso.get()
    .load(logoUrl)
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)
    .into(imageView, object : Callback {
        override fun onSuccess() {
            Toast.makeText(this@MainActivity, "Success!", Toast.LENGTH_SHORT)
                .show()
        }
        override fun onError(e: Exception?) {
            Toast.makeText(this@MainActivity, "Error!", Toast.LENGTH_SHORT)
                .show()
        }
    })

As you can see, we need to write this@MainActivity instead of just this. Otherwise, it will point to the current object of the anonymous Callback class, instead of the Activity class which inherits from Context.

Network and Memory Policy

We can specify the network and memory policies we want to use as well. To do this, we utilize the networkPolicy() and memoryPolicy() methods.

networkPolicy() can take three enums: NO_CACHE, NO_STORE, and OFFLINE. You can read descriptions of them below:

  • NO_CACHE skips checking whether a photo with the relevant URI (Uniform Resource Identifier) is already in the disk cache, forcing a download from the internet every time.

  • NO_STORE skips saving the uploaded image to the disk cache.

  • OFFLINE forces the request to run through the disk cache only, skipping the network.

These options can also be combined, as shown in the below example:

.networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE)

The memoryPolicy memory cache setup is the same as the NetworkPolicy disk cache setup.

memoryPolicy() can take the NO_CACHE and NO_STORE enums:

  • NO_CACHE is used to send an image request without checking if it's cached in memory.

  • NO_STORE skips storing the final result in the memory cache, which can be handy when requesting an image you'll only need once.

Again, these options can be combined:

.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)

Conclusion

Today you have learned how to use the Picasso dynamic image loading library. Picasso makes downloading images from the internet quick and easy. It can also be used to crop and transform them, set their priority, and specify whether they should be cached. Now it's time to check how much you can remember about the various methods!

38 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo