Quantcast
Channel: Envato Tuts+ Code - Mobile Development
Viewing all articles
Browse latest Browse all 1836

Code an Image Gallery Android App With Picasso

$
0
0
tag:code.tutsplus.com,2005:PostPresenter/cms-30966
Final product image
What You'll Be Creating

Picasso is a popular open-source Android library for loading both local and remote images. Learn how to easily use it for handling your image loading needs. 

1. What Is Picasso?

Picasso (name inspired by the famous French artist Pablo Picasso) is a very popular open-source Android library for loading images in your Android app. According to the official docs, it states:

...Picasso allows for hassle-free image loading in your application—often in one line of code!

Note that Picasso uses OkHttp (a network library from the same developer) under the hood to load the images over the internet. 

2. So Why Use Picasso?

Now you have learned what Picasso is all about, the next question you might ask is why use it?

Developing your own media loading and display functionality in Java or Kotlin can be a real pain: you have to take care of caching, decoding, managing network connections, threading, exception handling, and more. Picasso is an easy to use, well planned, well documented, and thoroughly tested library that can save you a lot of precious time—and save you some headaches. 

Here are many of the common pitfalls of loading images on Android that are dealt with for you by Picasso, according to the official docs:

  • handling ImageView recycling and download cancellation in an adapter
  • complex image transformations with minimal memory use
  • automatic memory and disk caching

Adding images to your app can make your Android app come alive. So in this tutorial, we'll learn about Picasso 2 by building a simple image gallery app. It will load the images via the internet and display them as thumbnails in a RecyclerView, and when a user clicks on an image, it will open a detail activity containing the larger image. 

A sample project (in Kotlin) for this tutorial can be found on our GitHub repo so you can easily follow along.

Good artists copy, great artists steal. — Pablo Picasso

3. Prerequisites

To be able to follow this tutorial, you'll need:

Fire up Android Studio and create a new project (you can name it PicassoDemo) with an empty activity called MainActivity. Make sure to also check the Include Kotlin support check box.

Android Studios Add an Activity to Mobile dialog

4. Declare Dependencies

After creating a new project, specify the following dependencies in your build.gradle. At the time of writing, the latest version of Picasso is 2.71828

Or with Maven:

Make sure you sync your project after adding Picasso and the RecyclerView v7 artifacts.

5. Add Internet Permission

Because Picasso is going to perform a network request to load images via the internet, we need to include the permission INTERNET in our AndroidManifest.xml

So go do that now!

Note that this is only required if you're going to load images from the internet. This is not required if you are only loading images locally on the device. 

6. Create the Layout

We'll start by creating our RecyclerView inside the activity_main.xml layout file. 

Creating the Custom Item Layout

Next, let's create the XML layout (item_image.xml) that will be used for each item (ImageView) within the RecyclerView

Now that we have created the layouts needed for our simple gallery app, the next step is to create the RecyclerView adapter for populating data. Before we do that, though, let's create our simple data model. 

7. Create a Data Model

We are going to define a simple data model for our RecyclerView. This model implements Parcelable for high-performance transport of data from one component to another in Android. In our case, data will be transported from SunsetGalleryActivity to SunsetPhotoActivity

Note that this model SunsetPhoto has only a single field called url (for demo purposes), but you can have more if you want. This class implements Parcelable, which means we have to override some methods. 

We can make use of Android Studio IDEA to generate these methods for us, but the downside to this is maintenance. How? Anytime we add any new fields to this class, we might forget to explicitly update the constructor and writeToParcel methods, which can lead to some bugs if we don't update the methods.  

Now, to circumvent updating or writing these boilerplate methods, Kotlin 1.1.14 introduced the @Parcelize annotation. This annotation will help us generate the writeToParcel, writeFromParcel, and describeContents methods automatically under the hood for us. 

Now, our code SunsetPhoto class is just two lines! Awesome! 

Remember to add the following code to your app module build.gradle:

In addition, I included a companion object (or static method in Java) getSunsetPhotos() in the SunsetPhoto model class that will simply return an ArrayList of SunsetPhoto when called.

8. Create the Adapter

We'll create an adapter to populate our RecyclerView with data. We'll also implement a click listener to open the detail activity—SunsetPhotoActivity—passing it an instance of SunsetPhoto as an intent extra. The detail activity will show a close-up of the image. We'll create it in a later section.

Notice that we used the apply extension function to put an object as extra to the intent. As a reminder, the apply function returns the object passed to it as an argument (i.e. the receiver object). 

9. Loading Images From a URL

We're going to need Picasso to do its job in this section—not to paint us a work of art, but to fetch images from the internet and display them. We'll display these images individually in their respective ImageViews inside our RecyclerView onBindViewHolder() method as the user scrolls the app. 

Step by step, here's what the calls to Picasso are doing:

The get() Method

This returns the global Picasso instance (singleton instance) initialized with the following default configurations: 

  • LRU memory cache of 15% the available application RAM.
  • Disk cache of 2% storage space up to 50MB but no less than 5MB. Note: this is only available on API 14+.
  • Three download threads for disk and network access.

Note that if these settings do not meet the requirements of your application, you're free to construct your own Picasso instance with full control of these configurations by using Picasso.Builder

Finally, you call the build() method to return you a Picasso instance with your own configurations. 

It's recommended you do this in your Application.onCreate and then set it as the singleton instance with Picasso.setSingletonInstance in that method—to make sure that the Picasso instance is the global one.

The load() Method 

load(String path) starts an image request using the specified path. This path can be a remote URL, file resource, content resource, or Android resource.

  • placeholder(int placeholderResId): a local placeholder resource id or drawable to be used while the image is being loaded and then displayed. It serves as a good user experience to display a placeholder image while the image is downloading.  

Note that Picasso first checks if the image requested is in the memory cache, and if it is, it displays the image from there (we'll discuss caching in Picasso more in a later section).

Other Methods

  • error(int errorResId): a drawable to be used if the requested image could not be loaded—probably because the website is down. 
  • noFade(): Picasso always fades in the image to be displayed into the ImageView. If you don't want this fade-in animation, simply call the noFade() method. 
  • into(ImageView imageView): the target image view into which the image will be placed.

Image Resizing and Transformation

If the server you are requesting the image from doesn't give you the image you need in the required size, you can easily resize that image using resize(int targetWidth, int targetHeight). Calling this method resizes the image and then displays it on the ImageView. Note that the dimensions are in pixels (px), not dp. 

You can pass in an Android dimension resource for both the width and height using the method resizeDimen(int targetWidthResId, int targetHeightResId). This method will convert the dimension size to raw pixels and then call resize() under the hood—passing the converted sizes (in pixels) as arguments. 

Note that these resize methods won't respect aspect ratio. In other words, your image aspect ratio can be distorted. 

Fortunately, Picasso gives us some useful methods to solve this issue: 

  • centerCrop(): scales the image uniformly (maintaining the image's aspect ratio) so that the image fills up the given area, with as much of the image showing as possible. If needed, the image will be cropped horizontally or vertically to fit. Calling this method crops an image inside of the bounds specified by resize().
  • centerInside(): scales the image so that both dimensions are equal to or less than the requested bounds. This will center an image inside of the bounds specified by resize()
  • onlyScaleDown(): only resize an image if the original image size is bigger than the target size specified by resize().
  • fit(): attempt to resize the image to fit exactly into the target ImageView's bounds.

Image Rotation

Picasso has an easy API to rotate an image and then display that image. The rotate(float degrees) method rotates the image by the specified degrees.

In the example above, this would rotate the image by 90 degrees. The rotate(float degrees, float pivotX, float pivotY) method rotates the image by the specified degrees around a pivot point.

Here we are going to rotate the image by 30 degrees around the pivot point 200, 100 pixels. 

Transformation

Apart from just manipulating an image by rotating it, Picasso also gives us the option to apply a custom transformation to an image before displaying it.  

You simply create a class that implements the Picasso Transformation interface. You then have to override two methods: 

  • Bitmap transform(Bitmap source): this transforms the source bitmap into a new bitmap. 
  • String key(): returns a unique key for the transformation, used for caching purposes.

After you're done creating your custom transformation, you simply execute it by invoking transform(Transformation transformation) on your Picasso instance. Note that you can also pass a list of Transformation to transform()

Here, I applied a circle crop transformation to the image from the Picasso Transformations open-source Android library. This library has many transformations you can apply to an image with Picasso—including transformations for blurring or grey-scaling an image. Go check it out if you want to apply some cool transformations to your images.  

10. Initializing the Adapter

Here, we simply create our RecyclerView with GridLayoutManager as the layout manager, initialize our adapter, and bind it to the RecyclerView

11. Creating the Detail Activity

Create a new activity and name it SunsetPhotoActivity. We get the SunsetPhoto extra and load the image—inside onStart()—with Picasso as we did before. 

The Detail Layout

Here's a layout to display the detail activity. It just displays an ImageView that will show the full-resolution version of the loaded image. 

12. Caching Mechanism in Picasso

If you observe carefully, you'll notice that when you revisit an image that was previously loaded, it loads even faster than before. What made it faster? It's Picasso's caching mechanism, that's what.

Here is what's going on under the hood. After an image has been loaded once from the internet, Picasso will cache it both in memory and on disk, saving repeated network requests and permitting faster retrieval of the image. When that image is needed again, Picasso will first check if the image is available in memory, and if it's there, it will return it immediately. If that image is not in memory, Picasso will check the disk next, and if it's there, it returns it. If it's not there, Picasso will finally do a network request for that image and display it. 

In summary, here's what goes on (under the hood) for an image request: memory -> disk -> network. 

Depending on your application, though, you might want to avoid caching—for example, if the images being displayed are likely to change often and not be reloaded.

So How Do You Disable Caching? 

You can avoid memory caching by calling memoryPolicy(MemoryPolicy.NO_CACHE). This will simply skip the memory cache lookup when processing an image request. 

Note that there is another enum: MemoryPolicy.NO_STORE. This is useful if you are very certain that you will only request an image once. Applying this will also not store the image in the memory cache—thereby not forcing out other bitmaps from the memory cache. 

But be very aware that the image will still be cached on the disk—to prevent that also, you use networkPolicy(@NonNull NetworkPolicy policy, @NonNull NetworkPolicy... additional), which takes one or more of the following enum values:

  • NetworkPolicy.NO_CACHE: skips checking the disk cache and forces loading through the network.
  • NetworkPolicy.NO_STORE: skips storing the result into the disk cache.
  • NetworkPolicy.OFFLINE: forces the request through the disk cache only, skipping the network.

To avoid both memory and disk caching altogether, just call both methods one after the other:

13. Request Listeners

In Picasso, you can implement a listener or callback to monitor the status of the request you made as the image loads. Only one of these methods will be called if you implement the Target interface on a request. 

  • void onBitmapFailed(e: Exception?, errorDrawable: Drawable?): triggered whenever the image could not be successfully loaded. Here, we can access the exception that was thrown. 
  • void onBitmapLoaded(Bitmap bitmap, LoadedFrom from): fired whenever an image has been successfully loaded. Here, we get the Bitmap to show the user. 
  • void onPrepareLoad(Drawable placeHolderDrawable): invoked right before your request is submitted. 

Here you could also show and then hide a progress dialog if you had one. 

There is another callback listener you can implement if you want, called Callback. This interface has just two methods: onSuccess() and onError(Exception e). The former is called when the image request load was successful, and the later is called when there is an error in processing the request. 

Going back to our image gallery app (inside SunsetPhotoActivity), let's modify the display a little by using a Callback object that will set the bitmap to the ImageView and also change the background colour of the layout by extracting the dark and vibrant colour of our image using the Android Palette API

So include the palette artifact in your app module's build.gradle:

Let's now implement the Callback interface in our Picasso request. 

14. Testing the App

Finally, you can run the app! Click on a thumbnail to get a full-sized version of the image.

Final app result

15. Prioritizing Requests

When you want to load different images at the same time on the same screen, you have the option to order which one is more important than the other. In other words, you can load important images first. 

You simply call priority() on your Picasso request instance and pass in any of the enums: Priority.LOW, Priority.NORMAL, or Priority.HIGH

16. Tagging Requests

By tagging your Picasso requests, you can resume, pause, or cancel requests which are associated with specific tags. Depending on your use case, you can tag your requests with a string or objects that should define the scope of the request as a Context, an Activity, or a Fragment. You can easily tag a Picasso request by calling tag(@NonNull Object tag) on one. Pass it an instance of Object which serves as the tag. 

Here are the following operations you can perform on tagged Picasso requests:

  • pauseTag(Object tag): pause all requests associated with the given tag. 
  • resumeTag(Object tag): resume paused requests with the given tag.
  • cancelTag(Object tag): cancel any existing requests with the given tag.

Though tagging your requests gives you some control over your requests, you should be very careful when using tags because of the potential for memory leaks. Here's what the official documentation says:

Picasso will keep a reference to the tag for as long as this tag is paused and/or has active requests. Look out for potential leaks.

Loading From the File System

It's straightforward to load images locally in your app.

Conclusion

Nice job! In this tutorial, you've built a complete image gallery app with Picasso, and along the way you've learned how the library works and how you can integrate it into your own project. 

You've also learned how to display both local and remote images, tagging requests, prioritizing requests, and how to apply image transformations like resizing. Not only that, but you've seen how easy it is to enable and disable caching, error handling, and custom request listeners. 

To learn more about Picasso, you can refer to its official documentation. To learn more about coding for Android, check out some of our other courses and tutorials here on Envato Tuts+!

2018-05-24T13:00:00.000Z2018-05-24T13:00:00.000ZChike Mgbemena

Viewing all articles
Browse latest Browse all 1836

Trending Articles