Building a Grid Layout With RecyclerView and Realm

Android — Estimated Time: 25 minutes

In this tutorial, Thorben Primke shows you how to use RealmRecyclerView, a specialized subclass of RecyclerView, to build a Grid Layout from data stored in Realm.

Have you been looking to build a grid layout in your app? With your data stored in Realm, you can create an interface like this really quickly. Best of all, changes to your data will trigger automatic animations!

Let’s get started, but if you want to jump to the end product, you can see the final code on GitHub here.

Here’s a demo of what the end result should look like:

RealmRecyclerView Demo

Tutorial

Please Note: You will need an API Key from the NY Times to pull data from their API. You can get an API Key for free from the NY Times Developer site.

Create a new Android Studio project, using the “Empty Activity” template.

Next, you’ll want to add RealmSearchView to your project. RealmSearchView is available as a gradle dependency via jitpack.io.

To add it, add the following line to your project’s build.gradle file.

maven { url "https://jitpack.io" }

The result may look like this:

allprojects {
    repositories {
        jcenter()
        maven { url "https://jitpack.io" }
    }
}

Next, in your app module’s build.gradle file, add the following line:

compile 'com.github.thorbenprimke:realm-recycler-view:0.9.4'

For this tutorial we are also going to use a sample data set and a image loading library. To add them to your app, add the following to your app module’s build.gradle file:

compile 'com.github.thorbenprimke:realm-nytimes-data:0.9.2'
compile 'com.github.bumptech.glide:glide:3.6.1'

That’s it, re-sync the dependencies and you are ready to go.

Integration

Now that we have our base project set up, it’s time to integrate the RealmRecyclerView and then subclass the RealmBasedRecyclerViewAdapter to customize the display of the RealmRecyclerView.

Open the MainActivity’s layout file and replace the contents with the following:

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <co.moonmonkeylabs.realmrecyclerview.RealmRecyclerView
        android:id="@+id/realm_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:rrvIsRefreshable="false"
        app:rrvLayoutType="Grid"
        app:rrvGridLayoutSpanCount="2"
        />
</FrameLayout>

Note here that we specify the three custom attributes for the view:

  • rrvIsRefreshable - This disables the pull to refresh feature to the RecyclerView.
  • rrvLayoutType - RealmRecyclerView supports different layout types and for this tutorial we want a ‘Grid’ with two columns.
  • rrvGridLayoutSpanCount - This attribute needs to be set to anything greater than zero when the rrvLayoutType is set to Grid.

In the sample data, we have a class called NYTimesStory (source). We will use this class as the type type in the adapter in order to render a story.

The NYTimesStory is rendered with a set of views and the result should look like this:

_------------------_
       Image
       Date
       Title
      Abstract
_------------------_

The view that will be used by the ViewHolder in the RealmBasedRecyclerViewAdapter will be named grid_item_view.xml. Create this layout in your layouts folder with the following contents.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/image"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginTop="5dp"
        android:layout_gravity="center_horizontal"/>

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:singleLine="true"
        android:ellipsize="end"
        android:textStyle="bold"/>

    <TextView
        android:id="@+id/date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:layout_gravity="center_horizontal"
        android:singleLine="true"
        android:ellipsize="end"/>

    <TextView
        android:id="@+id/story_abstract"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:layout_marginBottom="5dp"
        android:gravity="center_horizontal"
        android:maxLines="5"
        android:ellipsize="end"/>
</LinearLayout>

The data model will map to the above layout in order to render a NYTimesStory to the screen.

Now that we understand the data model and how it will look on the screen we need to subclass the RealmBasedRecyclerViewAdapter class:

public class NYTimesStoryRecyclerViewAdapter extends RealmBasedRecyclerViewAdapter<NYTimesStory,
        NYTimesStoryRecyclerViewAdapter.ViewHolder> {

    public NYTimesStoryRecyclerViewAdapter(
            Context context,
            RealmResults<NYTimesStory> realmResults,
            boolean automaticUpdate,
            boolean animateIdType) {
        super(context, realmResults, automaticUpdate, animateIdType);
    }

    public class ViewHolder extends RealmViewHolder {

        public TextView title;
        public TextView publishedDate;
        public ImageView image;
        public TextView storyAbstract;

        public ViewHolder(LinearLayout container) {
            super(container);
            this.title = (TextView) container.findViewById(R.id.title);
            this.publishedDate = (TextView) container.findViewById(R.id.date);
            this.image = (ImageView) container.findViewById(R.id.image);
            this.storyAbstract = (TextView) container.findViewById(R.id.story_abstract);
        }
    }

    @Override
    public ViewHolder onCreateRealmViewHolder(ViewGroup viewGroup, int viewType) {
        View v = inflater.inflate(R.layout.grid_item_view, viewGroup, false);
        ViewHolder vh = new ViewHolder((LinearLayout) v);
        return vh;
    }

    @Override
    public void onBindRealmViewHolder(ViewHolder viewHolder, int position) {
        final NYTimesStory nyTimesStory = realmResults.get(position);
        viewHolder.title.setText(nyTimesStory.getTitle());
        viewHolder.publishedDate.setText(nyTimesStory.getPublishedDate());
        final RealmList<NYTimesMultimedium> multimedia = nyTimesStory.getMultimedia();
        if (multimedia != null && !multimedia.isEmpty()) {
            Glide.with(GridExampleActivity.this).load(
                    multimedia.get(0).getUrl()).into(viewHolder.image);
        } else {
            viewHolder.image.setImageResource(R.drawable.nytimes_logo);
        }
        viewHolder.storyAbstract.setText(nyTimesStory.getStoryAbstract());
    }
}

The onCreateRealmViewHolder method inflates the grid_item_view and passes it to the ViewHolder.

In the onBindRealmViewHolder method the NYTimesStory is bound to the view.

Note here that we use the Glide image loading library to load the remote story image.

Connecting The Pieces

We are almost up and running. Now we need to connect the RealmRecyclerView and the NYTimesStoryRecyclerViewAdapter.

To wire them up, implement the following code in your activity:

private RealmRecyclerView realmRecyclerView;
private NYTimesStoryRecyclerViewAdapter nyTimesStoryAdapter;
private Realm realm;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main_grid_layout);
    realmRecyclerView = (RealmRecyclerView) findViewById(R.id.realm_recycler_view);

    setTitle(getResources().getString(
            R.string.activity_layout_name,
            getIntent().getStringExtra("Type")));

    resetRealm();

    Realm.setDefaultConfiguration(getRealmConfig());
    realm = Realm.getDefaultInstance();
    RealmResults<NYTimesStory> nyTimesStories =
            realm.where(NYTimesStory.class).findAllSorted("sortTimeStamp", false);
    nyTimesStoryAdapter = new NYTimesStoryRecyclerViewAdapter(this, nyTimesStories, true, true);
    realmRecyclerView.setAdapter(nyTimesStoryAdapter);

    final NYTimesDataLoader nyTimesDataLoader = new NYTimesDataLoader();
    nyTimesDataLoader.loadAllData(realm, "YOUR_NY_TIMES_API_KEY");
}

@Override
protected void onDestroy() {
    super.onDestroy();
    realm.close();
    realm = null;
}

private RealmConfiguration getRealmConfig() {
    return new RealmConfiguration
            .Builder(this)
            .setModules(Realm.getDefaultModule(), new NYTimesModule())
            .build();
}

private void resetRealm() {
    Realm.deleteRealm(getRealmConfig());
}

In the onCreate method of your activity the initial RealmResults are returned from the Realm query and provided to the NYTimesStoryRecyclerViewAdapter. Notice that the constructor of NYTimesStoryRecyclerViewAdapter has two boolean parameters. These ensure that the adapter automatically displays new Realm results for the original query and that the grid items are animated.

Also notice how the Realm’s default configuration is set via the getRealmConfig() method. This is required because the Realm’s schema needs to include the NYTimes models from the external library - com.github.thorbenprimke:realm-nytimes-data.

At this point you can run the application but the Realm is empty and nothing will be displayed.

Now that we have the initial wiring set up, we need to get the data ready. The New York Times offers a great API and we will use their “Top Stories” API to retrieve the latest news stories and display them in the grid.

Recall that during initial setup we added a gradle dependency for realm-nytimes-data. This library includes two Realm models: NYTimesStory (source) and NYTimesMultimedium (source) that represent the data returned from the New York Times Top Stories API.

To simplify this tutorial, we won’t go into all of the steps on how to request data from the API and then deserialize the resulting JSON data into Realm objects*. Instead, all you have to do is register for an API Key on the New York Times Developer site.

Then simply replace the YOUR_NY_TIMES_API_KEY value with your API key in your Activity code (snippet below):

nyTimesDataLoader.loadAllData(realm, "YOUR_NY_TIMES_API_KEY");

The loadAllData method loads and processes the API response and adds the stories to the Realm.

With the data loading in place, we can now run the application and should see the NYTimes news story headlines render in the grid as the example illustrates below.

RealmRecyclerView Demo

  • If you want to see how we handle the data request and JSON parsing, just check out the implementation of the method NYTimesDataLoader (source) in the sample data project. This method performs an async API request for the provided section and parses the JSON response into the Realm models.

Thorben Primke

Thorben Primke

Thorben is a Software Engineer at Pinterest working towards making all product pins buyable. Prior to Pinterest he worked on Android at Jelly, Facebook, and Gowalla.