How to use a RecyclerView to show images from storage
Tags: android, recyclerview
Published: 2019-02-19 18:38:13

---
title: How to use a RecyclerView to show images from storage
published: true
description: 
tags: Android, RecyclerView
canonical_url: https://www.learningsomethingnew.com/how-to-use-a-recycler-view-to-show-images-from-storage
---


![final](https://www.learningsomethingnew.com/android-recycler-view/final.gif)

## The issue at hand
The [RecyclerView](https://developer.android.com/guide/topics/ui/layout/recyclerview) widget is a more advanced and flexible version of ListView. It manages and optimizes the view holder bindings according to the scrolling position, and recycles the views so that it uses only a small number of views for a large number of list items.
Seeing as the RecyclerView [sample app](https://github.com/googlesamples/android-RecyclerView/) is outdated and doesn't even compile, this tutorial aims to show a relatively quick way to add a RecyclerView to modern Android Studio projects, and use it to display a list of random images we'll download to our device.

## Creating a new project
Make a new project (or open an existing one).
When creating the project, we'll choose to add a scrolling activity for this example, but you can choose any layout you want.

Run it now for a small sanity check:

![finished](https://www.learningsomethingnew.com/android-recycler-view/phase1-scrolling-view.png)

## Adding a list fragment
Right click on the project folder -> add -> fragment (list) -> finish

This creates a RecyclerView with lots of boilerplate code. Let's go over the added classes:

`MyItemRecyclerViewAdapter` - Creates the view holder which, well, holds the views for items in the list and binds the data to the views inside the view holder.

`ItemFragment` - The fragment that holds and initializes the adapter.

`Dummy/DummyContent` - Dummy items for populating the list. We'll replace those with our picture items.

`fragment_item_list.xml` - contains the RecyclerView widget.

`fragment_item.xml` - layout of each item in the list.

Now we need to add the fragment we created to our activity.
In `content_scrolling.xml` replace the TextView with: 

```xml
<fragment android:name="com.example.myapplication.ItemFragment"
    android:id="@+id/main_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
```

We'll also make the activity implement our interaction listener interface:

```java
implements ItemFragment.OnListFragmentInteractionListener
```

After adding this to the activity class, you'll have to implement the onListFragmentInteraction method, you can do it automatically with the suggestion window. This is the auto-generated method that's added:

```java
@Override
public void onListFragmentInteraction(DummyContent.DummyItem item) {    
}
```

Run the project now to see that the list shows and scrolls:

![finished](https://www.learningsomethingnew.com/android-recycler-view/phase2-list-fragment.png)

## Replacing dummy content
In android studio, rename `DummyContent.java` to `PictureContent.java` (and the class name), and move it out of the `dummy` package. Delete the dummy package.
We'll also delete the `DummyItem` class, and create a POJO class `PictureItem` in a new file, containing the picture URI and creation date:

```java
class PictureItem {
    public Uri uri;
    public String date;
}
```

In `PictureContent` replace the `DummyItem` creation with a `PictureItem`  creation:
```java
public class PictureContent {
    static final List<PictureItem> ITEMS = new ArrayList<>();

    public static void loadImage(File file) {
        PictureItem newItem = new PictureItem();
        newItem.uri = Uri.fromFile(file);
        newItem.date = getDateFromUri(newItem.uri);
        addItem(newItem);
    }

    private static void addItem(PictureItem item) {
        ITEMS.add(0, item);
    }
}
```

Now we'll update `fragment_item.xml` to display our image item with an `ImageView` for the image and a `TextView` for the creation date:

```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/item_image_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/app_name"
        android:scaleType="centerInside"
        android:padding="10dp" />

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/text_margin"
        android:text="@string/created_at"
        android:textAppearance="?attr/textAppearanceListItem" />

    <TextView
        android:id="@+id/item_date_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/text_margin"
        android:textAppearance="?attr/textAppearanceListItem" />
    </LinearLayout>
</LinearLayout>
```

Finally, in `MyItemRecyclerViewAdapter`, replace the content to bind our new data fields to our new views:
```java
public class MyItemRecyclerViewAdapter extends RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder> {

    private final List<PictureItem> mValues;
    private final OnListFragmentInteractionListener mListener;

    public MyItemRecyclerViewAdapter(List<PictureItem> items, OnListFragmentInteractionListener listener) {
        mValues = items;
        mListener = listener;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.fragment_item, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        holder.mItem = mValues.get(position);
        holder.mImageView.setImageURI(mValues.get(position).uri);
        holder.mDateView.setText(mValues.get(position).date);

        holder.mView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (null != mListener) {
                    // Notify the active callbacks interface (the activity, if the
                    // fragment is attached to one) that an item has been selected.
                    mListener.onListFragmentInteraction(holder.mItem);
                }
            }
        });
    }

    @Override
    public int getItemCount() {
        return mValues.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public final View mView;
        public final ImageView mImageView;
        public final TextView mDateView;
        public PictureItem mItem;

        public ViewHolder(View view) {
            super(view);
            mView = view;
            mImageView = view.findViewById(R.id.item_image_view);
            mDateView = view.findViewById(R.id.item_date_tv);
        }
    }
}

```

## Load pictures

Now we'll populate the list with images saved in the device storage. Add the images loading methods to `PictureContent`:

```java
public static void loadSavedImages(File dir) {
    ITEMS.clear();
    if (dir.exists()) {
        File[] files = dir.listFiles();
        for (File file : files) {
            String absolutePath = file.getAbsolutePath();
            String extension = absolutePath.substring(absolutePath.lastIndexOf("."));
            if (extension.equals(".jpg")) {
                loadImage(file);
            }
        }
    }
}

private static String getDateFromUri(Uri uri){
    String[] split = uri.getPath().split("/");
    String fileName = split[split.length - 1];
    String fileNameNoExt = fileName.split("\\.")[0];
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String dateString = format.format(new Date(Long.parseLong(fileNameNoExt)));
    return dateString;
}

public static void loadImage(File file) {
    PictureItem newItem = new PictureItem();
    newItem.uri = Uri.fromFile(file);
    newItem.date = getDateFromUri(newItem.uri);
    addItem(newItem);
}
```

We're going to call `loadSavedImages` from our activity `ScrollingActivity`, so we first need to get a reference to the recycler view. Add two fields:
```java
private RecyclerView.Adapter recyclerViewAdapter;
private RecyclerView recyclerView;
```

Which will be lazy loaded in `onCreate`:

```java
if (recyclerViewAdapter == null) {
    Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.main_fragment);
    recyclerView = (RecyclerView) currentFragment.getView();
    recyclerViewAdapter = ((RecyclerView) currentFragment.getView()).getAdapter();
}
```

And in `onResume` we'll add a call to `loadSavedImages`:

```java
@Override
protected void onResume() {
    super.onResume();

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            loadSavedImages(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS));
            recyclerViewAdapter.notifyDataSetChanged();
        }
    });
}
```

> Notice we're loading the files from `DIRECTORY_DOWNLOADS` which is a convensional folder for storing downloaded files.

## Downloading the pictures
We'll download random pictures from [Lorem Picsum](https://picsum.photos/) whenever clicking the Floating Action Button, using the built in DownloadManager class.

Add the download method to `PictureContent`:
```java
public static void downloadRandomImage(DownloadManager downloadmanager, Context context) {
    long ts = System.currentTimeMillis();
    Uri uri = Uri.parse(context.getString(R.string.image_download_url));

    DownloadManager.Request request = new DownloadManager.Request(uri);
    request.setTitle("My File");
    request.setDescription("Downloading");
    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
    request.setVisibleInDownloadsUi(false);
    String fileName = ts + ".jpg";
    request.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, fileName);

    downloadmanager.enqueue(request);
}
```
This downloads the file to `DIRECTORY_DOWNLOADS` with the current timestamp as file name.

Set `image_download_url` in `strings.xml`:
```xml
<string name="image_download_url">https://picsum.photos/200/300/?random</string>
```
> Don't forget to add the INTERNET permission to the manifest

Now we need to handle the download complete event. Add the following to activity's `onCreate`:

```java
onComplete = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String filePath="";
        DownloadManager.Query q = new DownloadManager.Query();
        q.setFilterById(intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID));
        Cursor c = downloadManager.query(q);

        if (c.moveToFirst()) {
            int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
            if (status == DownloadManager.STATUS_SUCCESSFUL) {
                String downloadFileLocalUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
                filePath = Uri.parse(downloadFileLocalUri).getPath();
            }
        }
        c.close();
        PictureContent.loadImage(new File(filePath));
        recyclerViewAdapter.notifyItemInserted(0);
        progressBar.setVisibility(View.GONE);
        fab.setVisibility(View.VISIBLE);
    }
};

context.registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
```

This is the (quite verbose) way of getting the downloaded file name when the download manager completes the download. After getting `filePath` we call `PictureContent.loadImage`, which adds it to our list.

> Notice the call to `recyclerViewAdapter.notifyItemInserted(0)`. This will cause the list to refresh with the new item we've inserted (at index 0)

## Aside: creating a plus icon with Asset Studio
As the final touchup, we'll update the FAB's icon, using Android Studio's Asset Studio, for creating a vector material icon.
Right-click the res folder and select New > Vector Asset.
Click the Button and search for the keyword `add`:

![vector](https://www.learningsomethingnew.com/android-recycler-view/vector-asset-creation.png)

This will give us the plus material icon. Change color to white, and save the xml in the drawable folder.


That's it! Now we have a scrolling RecyclerView showing the downloaded pictures:

![final](https://www.learningsomethingnew.com/android-recycler-view/final.gif)


> Full source can be found [here](https://github.com/syonip/android-recycler-example)

> This article is cross posted from my [blog](https://www.learningsomethingnew.com/how-to-use-a-recycler-view-to-show-images-from-storage)