Building an Animated To-Do List With Realm

Android — Estimated Time: 15 minutes

Working with the RecyclerView is a standard skill for all Android developers. It is an extremely flexible class whenever a single column list of data is required. However, setting up the list view and binding it to your data can get quite tedious. When storing your data in Realm, RealmRecyclerView and RealmBasedRecyclerViewAdapter can be used to quickly set up your list and automatically bind your data to your view.

To demonstrate its functionality, we are going to build a basic to-do list app that animates the insertion and deletion of the rows.

RealmRecyclerView Demo

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

Tutorial

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

RealmRecyclerView 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.5'

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

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

Integration

Open the MainActivity’s layout file (content_main.xml) and replace the contents with the following:

<?xml version="1.0" encoding="utf-8"?>
<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"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <co.moonmonkeylabs.realmrecyclerview.RealmRecyclerView
        android:id="@+id/realm_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:rrvLayoutType="LinearLayout"
        app:rrvSwipeToDelete="true"
        app:rrvEmptyLayoutId="@layout/to_do_empty_view"
        />
</FrameLayout>

Note here that we specify three custom attributes: rrvLayoutType, rrvSwipeToDelete and rrvEmptyLayoutId.

  • rrvLayoutType - RealmRecyclerView supports different layout types and for this tutorial we want a ‘LinearLayout’.
  • rrvSwipeToDelete - This attribute is only supported with rrvLayoutType of LinearLayout. If set to true, swiping a row to delete is enabled. When swiped, the row is deleted from the Realm directly.
  • rrvEmptyLayoutId - A custom empty state view can be provided via this attribute. Whenever the list has no item, the empty state is shown.

In order to provide a custom empty state, a layout resource is provided by setting rrvEmptyLayoutId. Create a file in your res/layout directory with the name to_do_empty_view.xml and place the following content into the file:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center_horizontal"
        android:text="Looks like you are all done\n(or haven\'t added anything)!"
        android:textSize="24sp"
        />
</FrameLayout>

Next, RealmBasedRecyclerViewAdapter needs to be subclassed, but before we do that we need to implement the type it will display. In the sample we are using the following ToDoItem model class. Add this class to your project.

public class TodoItem extends RealmObject {

    @PrimaryKey
    private long id;

    private String description;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

We will use the TodoItem class in the adapter to render a to-do item and provide it as a type parameter.

The ToDoItem is rendered with a simple view that has a single TextView and looks like this:

_------------------_
  To-Do-Description
_------------------_

Create a layout file in your res/layout directory with the following contents:

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

    <TextView
        android:id="@+id/todo_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:padding="4dp"
        android:textSize="32sp"
        android:textColor="@android:color/white"
        />
</FrameLayout>

At this point you’re ready to create a class and extend the RealmBasedRecyclerViewAdapter.

In your activity insert the following code as a subclass into the Activity.

public class ToDoRealmAdapter
        extends RealmBasedRecyclerViewAdapter<TodoItem, ToDoRealmAdapter.ViewHolder> {

    public class ViewHolder extends RealmViewHolder {

        public TextView todoTextView;
        public ViewHolder(FrameLayout container) {
            super(container);
            this.todoTextView = (TextView) container.findViewById(R.id.todo_text_view);
        }
    }

    public ToDoRealmAdapter(
            Context context,
            RealmResults<TodoItem> realmResults,
            boolean automaticUpdate,
            boolean animateResults) {
        super(context, realmResults, automaticUpdate, animateResults);
    }

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

    @Override
    public void onBindRealmViewHolder(ViewHolder viewHolder, int position) {
        final TodoItem toDoItem = realmResults.get(position);
        viewHolder.todoTextView.setText(toDoItem.getDescription());
        viewHolder.itemView.setBackgroundColor(
                COLORS[(int) (toDoItem.getId() % COLORS.length)]
        );
    }
}

Notice that two methods in the ToDoRealmAdapter have been overloaded -

  • onCreateRealmViewHolder: This creates the viewHolder with the view.
  • onBindRealmViewHolder: This binds the story to the view.

In the onCreateRealmViewHolder method the to_do_item_view layout is inflated and it is passed to the ViewHolder.

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

Copy the following code into your MainActivity to define the COLORS array:

private static final int[] COLORS = new int[] {
        Color.argb(255, 28, 160, 170),
        Color.argb(255, 99, 161, 247),
        Color.argb(255, 13, 79, 139),
        Color.argb(255, 89, 113, 173),
        Color.argb(255, 200, 213, 219),
        Color.argb(255, 99, 214, 74),
        Color.argb(255, 205, 92, 92),
        Color.argb(255, 105, 5, 98)
};

Connecting The Pieces

Now that the ToDoRealmAdapter is set up, it’s time to put the pieces together.

In the onCreate method of your Activity, add the following code:

// 'realm' is a field variable
realm = Realm.getInstance(this);
RealmResults<TodoItem> toDoItems = realm
        .where(TodoItem.class)
        .findAllSorted("id", true);
ToDoRealmAdapter toDoRealmAdapter = new ToDoRealmAdapter(this, toDoItems, true, true);
RealmRecyclerView realmRecyclerView = (RealmRecyclerView) findViewById(R.id.realm_recycler_view);
realmRecyclerView.setAdapter(toDoRealmAdapter);

Here the initial RealmResult is queried and provided to the ToDoRealmAdapter.

Notice that the constructor has two boolean parameters. These ensure that the adapter automatically displays new Realm results for the original query and that the LinearLayout items are animated.

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

We need to create a UI for the user to be able to add ToDo items. To do this you’ll need to connect the FAB to a dialog into which the user can type the description of a to-do item.

First, let’s update the FAB’s icon to a ‘+’ icon. You can download it here (or right click and download the image below).

fab icon

Open the MainActivity’s layout file (activity_main.xml) and find the FloatingActionButton and set its src property to @drawable/ic_add_white_24dp.

Next, we will create an input dialog that will allow items to be added. You can simply copy the following code snippets.

Create a layout file in the res/layout directory with the name to_do_dialog_view.xml and insert the following xml layout code:

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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:gravity="center_horizontal"
        android:text="What do you want to accomplish?"
        android:textStyle="bold"
        android:textSize="18sp"
        />

    <EditText
        android:id="@+id/input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:singleLine="true"
        android:imeOptions="actionDone"
        android:hint="Task Description"
        android:inputType="textCapWords"
        />
</LinearLayout>

Back in your Activity we need to add two methods that will enable the dialog to be shown and new ToDo items to be added to the Realm. Add this code to your Activity class.

private void buildAndShowInputDialog() {
    final AlertDialog.Builder builder = new AlertDialog.Builder(ToDoActivity.this);
    builder.setTitle("Create A Task");

    LayoutInflater li = LayoutInflater.from(this);
    View dialogView = li.inflate(R.layout.to_do_dialog_view, null);
    final EditText input = (EditText) dialogView.findViewById(R.id.input);

    builder.setView(dialogView);
    builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            addToDoItem(input.getText().toString());
        }
    });
    builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            dialog.cancel();
        }
    });

    final AlertDialog dialog = builder.show();
    input.setOnEditorActionListener(
            new EditText.OnEditorActionListener() {
                @Override
                public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                    if (actionId == EditorInfo.IME_ACTION_DONE ||
                            (event.getAction() == KeyEvent.ACTION_DOWN &&
                                    event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
                        dialog.dismiss();
                        addToDoItem(input.getText().toString());
                        return true;
                    }
                    return false;
                }
            });
}

private void addToDoItem(String toDoItemText) {
    if (toDoItemText == null || toDoItemText.length() == 0) {
        Toast
                .makeText(this, "Empty ToDos don't get stuff done!", Toast.LENGTH_SHORT)
                .show();
        return;
    }

    realm.beginTransaction();
    TodoItem todoItem = realm.createObject(TodoItem.class);
    todoItem.setId(System.currentTimeMillis());
    todoItem.setToDo(toDoItemText);
    realm.commitTransaction();
}

The Realm related code is in addToDoItem. In the addToDoItem method a new ToDoItem is created and added to the Realm. Because the ToDoRealmAdapter has automatic updates enabled, any items added to the Realm will automatically be displayed in the list.

Before we are ready to try out our To-Do app, the FAB click event needs to be wired up to display the dialog. For this, add buildAndShowInputDialog() method to the respective onClickListener in onCreate method of your activity and remove any existing code beforehand. It should look like this:

fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        buildAndShowInputDialog();
    }
});

For reference, the full working example activity code is here.

With the to-do item ‘add dialog’ in place, we can now run the application and add to-do items. Create a few to see them displayed in the list with different color backgrounds. Swipe any of the items to delete them.

That’s it! Happy coding!

RealmRecyclerView Demo


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.