Building Realm Add-ons for Android

Realm is great, but your app has to be a lot more than just a database. You need a UI! Thorben explains how he built Realm add-ons for Android including Recycler View, Map View, and Search View, to help you more easily make a beautiful and performant UI powered by your data. He covers the techniques he used to connect the views to the data, & how to use the add-ons in your apps.


About Myself (0:22)

My name is Thorben. I work at Pinterest. Specifically, I work on the commerce team, and we’re trying to make pins buyable. I previously worked at a company called Jelly, and more specifically on a product Super there. I also worked on Facebook Messenger and search at Facebook, both on Android. And originally, I got started on Android at GoWalla, which was a location-based application or service similar to FourSquare.

How I Got Started Using Realm (1:12)

At Jelly, we pivoted to another product called Super, so we were starting from scratch. We needed a feed of things that people were posting, and there was also a detail screen where you could go to, that was like a post, as well as write comments. Between the different screens, we were always making network requests, and the options were getting out of sync. For example, if you “like” a detail, how do we get that data back to the feed to keep the same story in sync?

One approach we could have taken was an event pass - that is, send something over and pick it up. We could have taken all the network requests, cached them in a DB, as well as in memory, so that all the screens rely on the same in-memory object.

When I first discovered Realm, everybody talked about how fast it was, especially the fact that it didn’t have to go to the background thread to do any of your queries - you could always do it on your UI thread. As soon as Realm supported primary keys, I took all the SQL code out, which was much easier compared to the work at Remind because we hadn’t launched yet, and replace it with Realm. In the end, that solved any synchronization problems between all of my screens.

The Intentions Behind Realm Add-Ons (3:00)

Realm Add-ons came about as components that I built while building Super, but they’re reusable and the kind of thing that’s usually needed in most other applications. I also wanted them to be extensible so that I didn’t just build them for my own needs, but also for other people, so they’re on GitHub so people can customize them and edit them themselves.

These are the three I created:

  • RealmRecyclerView - a Realm component that’s built on top of the new RecyclerView.
  • RealmSearchView - a RecyclerView with a search bar at the top to type searches, that’s auto-filled with your Realm data as you type.
  • RealmMapView - a one-off component I built to match the iOS component, that probably not many people will have a use for, but is very neat and also the easiest to integrate.

RealmRecyclerView (4:32)

I always needed pull to refresh functionality to fetch new data from the server. In the case of Super, this was the feed as people posted more content. I also needed support for infinite scrolling and pagination.

The data might not necessarily be in your Realm, but you can kick off a network request, fetch more data, insert it into your Realm, and with the automatic updates, have your data displayed in your feed.

Something that is new with the RecyclerView is that you get animations for free. With listening to changes on the Realm, you kind of know that something’s changed, but you don’t have very fine-grained notifications yet about what changed.

Pull to Refresh (5:52)

For the pull to refresh, it’s using the Scribe refresh layout underneath. It requires a listener and that’s about it. So on the RealmRecyclerView, you just have your listener. If somebody initiates the pull to refresh, you get notified, and you can do whatever your logic requires.

realmRecyclerView.setOnRefreshListener(
  new RealmRecyclerView.OnRefreshListener() {
    @Override
    public void onRefresh() {
      asyncRefreshAllQuotes();
    }
  }
);

RealmRecyclerView.setRefreshing(false);

Infinite Scrolling (6:40)

This is very similar to the pull to refresh: once you get to the bottom of the list, you’ll want to load more things so you set up a LoadMoreListener. Again, you do your logic, either going to the network or doing something else that will get more stories, with the results inserted into your Realm.

realmRecyclerView.setOnLoadMoreListener(
  new RealmRecyclerView.OnLoadMoreListener() {
    @Override
    public void onLoadMore(Object lastItem) {
      asyncLoadMoreQuotes();
    }
  }
);

realmRecyclerView.disableShowLoadMore();

Change Animations (7:20)

Animation now comes for free with the RecyclerView as long as you tell it what was inserted or what was removed, or what particular rows changed. But for this, we need to know exactly what changed.

I rely on the onChange event on a Realm results set. But this only lets me know that something’s changed - I don’t know exactly what. So I maintain IDs separately within the RealmRecyclerView adaptor. To mirror what’s actually being displayed from the Realm, and to know when something’s changed, I can look all the IDs that are now available on the Realm and compare them to the old results.

We use another open source library for this, called difflib, to figure out what the difference is, which will tell me the delta.

Empty State (9:55)

This one is very simple. There’s a default empty state, but if you want to provide your own empty state, just create a view and provide that resource ID via custom attribute on the Realm RecyclerView in your XML layout.

Section Headers (10:17)

For section headers, I integrated a library that specifically works on providing section headers of different kinds. This one’s called SuperSLiM. It also provides the animation for inserting and deletion, and in the case of deletion it keeps track of which particular rows those are and removes not only the last item in that section but also the header at the same time.

Options (11:05)

  • Linear
  • Grid
  • Vertical LinearLayout with Section Headers

You can have a linear layout, either on vertical or horizontal. You can also have a grid, and that can have that vertical in your layout with the section headers.

  • Swipe to Remove

Another feature is swipe to remove. I added that on the rows so you can actually swipe it, and it will trigger the removal of that item from the Realm. And for the layout with grid, you can specify how many grid columns and what width they’re supposed to be.

RealmSearchView (12:00)

The RealmSearchView is something I’ve needed in the past more than once. Essentially, you have some content that you want filtered. This one actually extends the RealmRecyclerView as far as the bottom content where it displays the data but it comes with a search field on the top. You take this view component, drop it into your fragment or activity, and it will look as what is rendered on this slide.

realmSearchView = (RealmSearchView) findViewById(R.id.search_view);
realm = Realm.getDefaultInstance();
adaptor = new BlogRecyclerViewAdaptor(this, realm, "title");
realmSearchView.setAdaptor(adaptor);

public class BlogRecyclerViewAdaptor
        extends RealmSearchAdaptor<Blog, BlogRecyclerViewAdaptor.ViewHolder> {
          public BlogRecyclerViewAdaptor(
              Context context,
              Realm realm,
              String filterColumnName) {
                super(context, realm, filterColumnName);
              }
          )
        }

If you have any input, it will display the clear button. On the bottom there’s a specific adaptor, and you pass in, in this case block, which is the class of my model. And so it looks at this class and queries all the items available for it underneath, and that’s where the content for the search view comes from. In this case, we provided title as the field that we want to filter on, so as you type something it’s going to just look at the title field of this Realm object.

This is the most simplistic implementation, but another option is contains, so anywhere within, not just at the beginning. You can specify case sensitive, you can specify the order of the results, as well as the key, or the field that you want to sort by. And if you want to always show or prefilter your results by a particular term - let’s say you have some mixed data or content within that particular Realm or class - you can always provide the base predicate to actually prefill data results, and then the user types the additional search term on top.

public void filter(String input) {
  RealmResults<T> businesses;
  RealmQuery<T> where = realm.where(clazz);
  if (input.isEmpty() && basePredicate != null) {
    if (useContains) {
      where = where.contains(filterKey, basePredicate, casing);
    } else {
      where = wehre.beginsWith(filterKey, basePredicate, casing);
    }
  } else if (!input.isEmpty()) {
    if (useContains) {
      where = where.contains(filterKey, input, casing);
    } else {
      where = where.beginsWith(filterKey, input, casing);
    }
  }

  if (sortKey == null) {
    businesses = where.findAll();
  } else {
    businesses = where.findAllSorted(sortKey, sortOrder);
  }
  updateRealmResults(businesses);
}

The bulk of the logic, what this component does, is all within this one filter method, where it applies those different options every time the text field input changes. This rer-uns the query on every single keystroke, and I found that there was pretty much no lag, even when filtering the Realm live.

RealmMapView (14:50)

And the last, the third component is the RealmMapView. This is based on the SupportMapFragment. So it’s actually not really a view, but more a fragment that your fragment extends.

These support MapFragment, and for the clustering, it uses the MapUtils to group the data together. There’s logic that I built to provide the data in a form that the MapUtils clustering methods needed, which means extracting the lat long out of it to begin and build up that list, so the clustering can be done ahead of time.

It’s super easy to integrate, and all you have to provide is the type of the model, and three things: the title name, or the name of the business in this case, and the latitude and longitude.

public class BusinessRealmClusterMapFragment
          extends RealmClusterMapFragment<Business> {
            @Override
            protected String getTitleColumnName() {
              return "name";
            }

            @Override
            protected String getLatitudeColumnName() {
              return "latitude";
            }

            @Override
            protected String getLongitudeColumnName() {
              return "longitude";
            }
          }

All three of the Add-Ons are on GitHub, and they’re easily integratable with Gradle via JetPack.


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.