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:
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.
- 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.
Receive news and updates from Realm straight to your inbox