Lesson Tuesday

If you run our MyRestaurants app you'll probably notice that image quality in the RestaurantDetailFragment is pretty dismal. That doesn't make for a great user experience. Let's fix this!

In this lesson we will walk through retrieving higher-quality files from the Yelp API, and resizing them appropriately using Picasso in order to prevent OutOfMemory errors.

A quick Google search reveals that the Yelp API contains multiple image sizes to choose from, as detailed in this StackOverflow post. Using this tip, let's retrieve a higher-quality photo for each restaurant.

Images from the Yelp API Response

First, we'll add a method to our Restaurant model that will change the image's filename we parse out from the API response, therefore providing our app a higher-quality image:

Restaurant.java
@Parcel
public class Restaurant {
    ...

    public Restaurant(String name, String phone, String website, double rating, String imageUrl, String address, ArrayList<String> categories) {
        ...
        this.mImageUrl = getLargeImageUrl(imageUrl);
        ...
    }

   ….

    public String getLargeImageUrl(String imageUrl) {
        String largeImageUrl = imageUrl.substring(0, imageUrl.length() - 6).concat("o.jpg");
        return largeImageUrl;
    }
}

Here, our new method getLargeImageUrl() simply replaces the last characters in the image's filepath with the characters that correspond to the higher-quality version. If we run the app again we'll notice the image quality is much, much better.

But uh oh! After a few swipes it crashes, citing a java.lang.OutOfMemoryError error. Java and Android applications are only allowed a limited amount of memory, as specified during application setup. This error occurs when we attempt to add more data into memory, but don't have enough room for it.

It appears that loading multiple high-quality images is exceeding our allocated memory. Let's resize our images in order to make them more manageable for our app.

Resizing Images with Picasso

Restaurant Detail Fragment

Thankfully, Picasso comes with a handy method to resize images. We'll scale down our images before setting the ImageView source in our RestaurantDetailFragment in order to avoid consuming all that memory:

RestaurantDetailFragment.java
public class RestaurantDetailFragment extends Fragment {
    private static final int MAX_WIDTH = 400;
    private static final int MAX_HEIGHT = 300;

…..

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_restaurant_detail, container, false);
        ButterKnife.bind(this, view);

        Picasso.with(view.getContext())
                .load(mRestaurant.getImageUrl())
                .resize(MAX_WIDTH, MAX_HEIGHT)
                .centerCrop()
                .into(mImageLabel);

        …..
    }
}

Here, we create two static final variables to hold our values that correspond to the maximum width and height we'd like our images resized to. Then, using the resize() method built into Picasso, we scale the image down. We also call the centerCrop() method so that the image scales properly when filling the requested bounds. You can learn more about this and other Picasso methods on Picasso's website.

Now if we launch our app again, not only do our images still look clean and crisp, we can swipe through multiple RestaurantDetailFragments without crashing!

Restaurant List Adapter

Let's also resize images that appear in our RestaurantListAdapter. While we have not yet received an java.lang.OutOfMemoryError error due to this portion of our application, we theoretically could as our application grows.

Following the same general process we did in the RestaurantDetailFragment, we'll first define another two static final variables to hold maximum width and height values. Because the images appear even smaller here in our list than in our fragment, we'll restrict the image size to 200 x 200:

RestaurantListAdapter.java
public class RestaurantListAdapter extends RecyclerView.Adapter<RestaurantListAdapter.RestaurantViewHolder> {
    private static final int MAX_WIDTH = 200;
    private static final int MAX_HEIGHT = 200;

    private ArrayList<Restaurant> mRestaurants = new ArrayList<>();
    private Context mContext;
...

We're already calling upon Picasso to load our images in the bindRestaurant() method when we say Picasso.with(mContext).load(restaurant.getImageUrl()).into(mRestaurantImageView);. We'll include several more methods in this portion of the code to instruct Picasso to also resize and crop this image when it is loaded:

RestaurantListAdapter.java
...

 public void bindRestaurant(Restaurant restaurant) {

            Picasso.with(mContext)
                    .load(restaurant.getImageUrl())
                    .resize(MAX_WIDTH, MAX_HEIGHT)
                    .centerCrop()
                    .into(mRestaurantImageView);

            mNameTextView.setText(restaurant.getName());
            mCategoryTextView.setText(restaurant.getCategories().get(0));
            mRatingTextView.setText("Rating: " + restaurant.getRating() + "/5");
        }
...

Now our images should be crisp and clear throughout the application, yet appropriately re-sized to avoid consuming all our available memory. Perfect!


Example GitHub Repo for MyRestaurants