Android Architecture Components and Realm

There were several exciting announcements at Google IO 2017: too many to go through all in one post. The thing that stood out to me the most (other than Kotlin of course 😉) were the Android Architecture Components.

Google has finally given us some guidance around recommended architecture on Android and they’ve done so in a way that is modular, flexible and allows developers to plug in different modules and frameworks as needed.

In one of his talks, Yiğit Boyar, said that there are more components and recommendations coming but that they were ready to announce the following and show us how they work.

  • Room
  • ViewModel
  • Lifecycle
  • LiveData

If you didn’t get a chance to watch the architecture videos on these topics yet, I’d highly recommend them. There are a few good ones but this video is a good summary.

I’ll briefly explain each and how we can use them to simplify development with Realm.

Room

Room is Google’s new ORM built on top of SQLite. It’s a major improvement over the previous Google API for working with SQLite. The name also has a nice ring to it 😉

Unlike many other SQLite ORMs, Room requires you to write the SQL to query for data and doesn’t support lazy loading of children in the objects returned. This is actually a strength over many other ORMs, that generate SQL for the developer under the hood. While lazy loading and not having to manually create and maintain SQL over time seems appealing at first, anyone who’s spent a fair amount of time using an ORM knows that as soon as you start following relationships between objects, new (sometimes large and inefficient) queries get run, which can greatly degrade performance.

While you still have to manually query and join data from the database, Room makes this easier by defining a @Query annotation that takes the SQL as its value. You attach that annotation to a Data Access Object (DAO) interface method that names the query and sets the return type. Room then generates the implementation DAOs for you at compile time.

Here are a few examples

@Query("SELECT Loan.id, Book.title as title, User.name as name, Loan.startTime, Loan.endTime " +
       "FROM Book " +
       "INNER JOIN Loan ON Loan.book_id = Book.id " +
       "INNER JOIN User ON User.id = Loan.user_id " +
       "WHERE User.name LIKE :userName " +
       "AND Loan.endTime > :after "
)
public LiveData<List<LoanWithUserAndBook>> findLoansByNameAfter(String userName, Date after);

@Query("SELECT * From Loan")
LiveData<List<Loan>> findAll();

If you’re using SQLite for local storage on Android, Room represents a major improvement in how you can work with it. For more information, check out the Google Room Documentation.

ViewModel

Google ViewModels are designed to provide access to UI related data to the Activity and combined with Google’s new LiveData and LifeCycle components it will change the way you write Android apps going forward.

Get more development news like this

The single best feature about this approach is that the ViewModel is lifecycle aware and isn’t destroyed on configuration changes, such as when the device rotates. This is an issue that has plagued Android apps for a long time. As you can see in the diagram taken from the android developer documentation, the ViewModel lives until the Activity finishes.

ViewModel Lifecycle Diagram

For Realm, this means that the Realm lifecycle can be managed in the ViewModel and closed when the ViewModel is no longer being used.

This all works because of a new concept called Lifecycles, or Lifecycle aware components.

Lifecycle

Most of the app components that are defined in the Android Framework have lifecycles attached to them. Realm is no different. For every Realm.getDefaultInstance() you invoke, you need to call realm.close() on that instance before it’s GC’d. In addition, you need to remove any change listeners and close any open transactions before closing the realm instance. The Android Lifecycle package has classes and interfaces that make it easier for you to build components that are “Lifecycle Aware”. We’ll see how this works in practice in the code sample later on, but first let’s talk about the last piece, LiveData.

LiveData

LiveData provides a way to stream data from your ViewModel to your Activity/UI without the Activity needing to poll the ViewModel for changes, or worse, make the ViewModel hold a reference to the Activity in order to update the display. Your Activity can bind to LiveData so that it reacts to data changes as they occur.

It is awesome to see Google embrace the Live Data concept. Their extensible LiveData class works really well with Realm’s observable live data, providing a layer of abstraction so that the Activity isn’t exposed to RealmResults and RealmObjects.

The best part of the new Architecture Components is it’s pluggable nature. Any data can be represented as LiveData. Any component can be made Lifecycle aware, and ViewModels can be written to meet the needs of any app. Google has done a great job of providing guidance and tools to help, without getting in the way. Cheers Google! 🎉

Let’s take a look at how this works with Realm using the Google Code labs android-persistence project as a starter and swapping the SQLite table structure with a Realm Object data model. Here is a before and after view.

android-persistence DataModel Comparison

The biggest difference is in the relationships. With Realm, both Users and Books have a collection of loans, and each Loan has a reference to the Book and User to which it belongs. Whereas, with SQLite, these relationships are inferred by Foreign Key (FK) references stored in the Loan table and joined via SQL Query joins as we’ll see in a moment.

I had to update the model classes to account for this, but not too much. Here is an example of the Loan model class, with a before and after.

LoanModelCode

Queries

Queries also have to change a bit. For example, instead of defining an interface method and SQL for findLoansByNameAfter, I need to provide a method body and RealmQuery.

Architecture Components Switching to Realm DAO

With the model changes in place, let’s see how it all fits together, starting from the top of the stack.

Activity

The Activity extends from LifecycleActivity. Eventually this will be merged into the AppCompat libraries. Doing this allows us to use Lifecycle aware Components, like ViewModels which we’ll see in a minute.

public class CustomResultUserActivity extends LifecycleActivity {

    private CustomResultViewModel mShowUserViewModel;
    private TextView mBooksTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.db_activity);
        mBooksTextView = (TextView) findViewById(R.id.books_tv);

        // Android will instantiate my ViewModel for me, and the best part is
        // the viewModel will survive configurationChanges!
        mShowUserViewModel = ViewModelProviders.of(this).get(CustomResultViewModel.class);

        // We'll observe updates to our LiveData loan string.
        mShowUserViewModel.getLoansResult().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String result) {
                mBooksTextView.setText(result);
            }
        });
    }

    public void onRefreshBtClicked(View view) {
        mShowUserViewModel.simulateDataUpdates();
    }
}

Notice that the onCreate gets an instance of the ViewModel using the built in ViewModelProviders.of(...). This will create a new CustomResultViewModel for us if there isn’t already one created for us in this Activity lifecycle. If the user rotates the phone and the resulting configuration change causes the Activity to be destroyed and recreated, the ViewModel will survive and the same instance will be returned in the next Activity.onCreate call. At the same time, when the Activity finishes and the ViewModel is no longer being used, the system will still give the ViewModel a chance to clear resources before it destroys it.

The ViewModel exposes LiveData to the activity via mShowUserViewModel.getLoansResult() and the Activity observes changes. We don’t need to add code to stop observing in onPause() or onStop(), because LiveData is lifecycle aware and bound to the lifecycle of this Activity. That’s why we passed the activity in as the first argument to .observe(...).

ViewModel

The ViewModel stores and manages the UI data and exposes actions to the Activity, like simulateDataUpdates().

public class CustomResultViewModel extends ViewModel {

    private Realm mDb;
    private LiveData<String> mLoansResult;

    public CustomResultViewModel() {
        mDb = Realm.getDefaultInstance();
        subscribeToMikesLoansSinceYesterday();
        simulateDataUpdates();
    }

    public void simulateDataUpdates() {
        DatabaseInitializer.populateAsync(mDb);
    }

    public LiveData<String> getLoansResult() {
        return mLoansResult;
    }

    private void subscribeToMikesLoansSinceYesterday() {
        LiveRealmData<Loan> loans = loanModel(mDb)
                .findLoansByNameAfter("Mike", getYesterdayDate());
        mLoansResult = Transformations.map(loans, new Function<RealmResults<Loan>, String>() {
            @Override
            public String apply(RealmResults<Loan> loans) {
                StringBuilder sb = new StringBuilder();
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm",
                        Locale.US);

                for (Loan loan : loans) {
                    sb.append(String.format("%s\n  (Returned: %s)\n",
                            loan.getBook().getTitle(),
                            simpleDateFormat.format(loan.getEndTime())));
                }
                return sb.toString();
            }
        });
    }

    /**
     * This method will be called when this ViewModel is no longer used and will be destroyed.
     * <p>
     * It is useful when ViewModel observes some data and you need to clear this subscription to
     * prevent a leak of this ViewModel... Like the Realm instance!
     */
    @Override
    protected void onCleared() {
        mDb.close();
        super.onCleared();
    }

    private Date getYesterdayDate() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.DATE, -1);
        return calendar.getTime();
    }
}

The Realm version of the CustomResultViewModel is very similar to the Google Code Labs version except that instead of creating a separate DTO to hold Loan and User data, I just reference a Loan, which references the User it belongs too. This isn’t possible to do efficiently with an ORM and SQL. With Realm we don’t have this limitation because relationships are essentially free with Realm. There are no joins happening and no additional queries being run. Realm relationships are, simply, an object graph.

Finally, LiveRealmData<T> is-a LiveData<RealmResults<T>> as you see here.

public class LiveRealmData<T extends RealmModel> extends LiveData<RealmResults<T>> {

    private RealmResults<T> results;
    private final RealmChangeListener<RealmResults<T>> listener = 
        new RealmChangeListener<RealmResults<T>>() {
            @Override
            public void onChange(RealmResults<T> results) { setValue(results);}
    };

    public LiveRealmData(RealmResults<T> realmResults) {
        results = realmResults;
    }

    @Override
    protected void onActive() {
        results.addChangeListener(listener);
    }

    @Override
    protected void onInactive() {
        results.removeChangeListener(listener);
    }
}

This is a wrapper for the RealmResults to expose them as Lifecycle aware LiveData.

DAOs

Hiding your database interactions behind DAOs is a good idea for interoperability with other components and can help with testing. For example, the ViewModels can be tested independently of the DAOs, which can be mocked for unit testing.

The Codelab example uses inheritance to add methods onto a RoomDatabase and has a factory method to get a singleton instance of it like so:

@Database(entities = {User.class, Book.class, Loan.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

    private static AppDatabase INSTANCE;

    public abstract UserDao userModel();
    public abstract BookDao bookModel();
    public abstract LoanDao loanModel();

    public static AppDatabase getInMemoryDatabase(Context context) {
        if (INSTANCE == null) {
            INSTANCE =
                    Room.inMemoryDatabaseBuilder(context.getApplicationContext(), AppDatabase.class)
                    // To simplify the codelab, allow queries on the main thread.
                    // Don't do this on a real app! See PersistenceBasicSample for an example.
                    .allowMainThreadQueries()
                    .build();
        }
        return INSTANCE;
    }

    public static void destroyInstance() {
        INSTANCE = null;
    }
}

While we don’t need to define a custom factory lookup with Realm (because Realm already provides one and the lifecycle is bound to the ViewModels lifecycle in this case), it would be nice for us to be able to get the instance of the DAOs associated with a given Realm instance, as needed. As a reminder, all realm models are tied to the lifecycle of the Realm instance from which they were fetched.

To accomplish this, I could have written a simple RealmUtils.java which does this… but that would be boring 😉. So I made use of the now (fully supported!) Kotlin Extensions functionality instead.

@file:JvmName("RealmUtils") // pretty name for utils class if called from Java

...
fun Realm.userModel(): UserDao = UserDao(this)
fun Realm.bookModel(): BookDao = BookDao(this)
fun Realm.loanModel(): LoanDao = LoanDao(this)

// Convenience extension on RealmResults to return as LiveRealmData
fun <T:RealmModel> RealmResults<T>.asLiveData() = LiveRealmData<T>(this)

Now, in Java code, I can still call RealmUtils.bookDao(realm) to get a book DAO, but if I’m accessing from Kotlin code, I can simply say realm.bookDao(), to get a bookDao.

Now that I have a way to create DAOs, let’s take a look at a sample Realm DAO. This is the LoanDao used by the CustomResultViewModel to find loans by name, after a specific date.

public class LoanDao  {

    private Realm mRealm;

    public LoanDao(Realm realm) { this.mRealm = realm; }

    public LiveRealmData<Loan> findLoansByNameAfter(final String userName, final Date after) {
        return asLiveData(mRealm.where(Loan.class)
                .like("user.name", userName)
                .greaterThan("endTime", after)
                .findAllAsync());
    }

    public void addLoan(final Date from, final Date to, final String userId, final String bookId) {
        User user = mRealm.where(User.class).equalTo("id", userId).findFirst();
        Book book = mRealm.where(Book.class).equalTo("id", bookId).findFirst();
        Loan loan = new Loan(from, to, book, user);
        mRealm.insert(loan);
    }
}

The last thing we need to do is to recreate the live data feed simulation from the reference example.

Simulating Data Updates

When the Activity starts, and again whenever the refresh button is tapped, the data is cleared and new simulation data is put into the database.

I modified the SQLite example to do this. My realm simulation code is very similar to the original except that I’m pushing the work to the background using Realm’s built in Async transaction methods, instead of an AsyncTask. I might also use an IntentService for this type of work in a real world Realm App.

    // Simulate a blocking operation delaying each Loan insertion with a delay:
    private static final int DELAY_MILLIS = 500;

    public static void populateAsync(final Realm db) {

        Realm.Transaction task = populateWithTestDataTx;
        db.executeTransactionAsync(task);
    }

    private static Realm.Transaction populateWithTestDataTx = new Realm.Transaction() {
        @Override
        public void execute(Realm db) {

            db.deleteAll();
            checkpoint(db);

            User user1 = addUser(db, "1", "Jason", "Seaver", 40);
            User user2 = addUser(db, "2", "Mike", "Seaver", 12);
            addUser(db, "3", "Carol", "Seaver", 15);

            Book book1 = addBook(db, "1", "Dune");
            Book book2 = addBook(db, "2", "1984");
            Book book3 = addBook(db, "3", "The War of the Worlds");
            Book book4 = addBook(db, "4", "Brave New World");
            addBook(db, "5", "Foundation");
            try {
                // Loans are added with a delay, to have time for the UI to react to changes.

                Date today = getTodayPlusDays(0);
                Date yesterday = getTodayPlusDays(-1);
                Date twoDaysAgo = getTodayPlusDays(-2);
                Date lastWeek = getTodayPlusDays(-7);
                Date twoWeeksAgo = getTodayPlusDays(-14);

                addLoan(db, user1, book1, twoWeeksAgo, lastWeek);
                Thread.sleep(DELAY_MILLIS);
                addLoan(db, user2, book1, lastWeek, yesterday);
                Thread.sleep(DELAY_MILLIS);
                addLoan(db, user2, book2, lastWeek, today);
                Thread.sleep(DELAY_MILLIS);
                addLoan(db, user2, book3, lastWeek, twoDaysAgo);
                Thread.sleep(DELAY_MILLIS);
                addLoan(db, user2, book4, lastWeek, today);
                Log.d("DB", "Added loans");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    private static Date getTodayPlusDays(int daysAgo) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.DATE, daysAgo);
        return calendar.getTime();
    }

    private static void checkpoint(Realm db) {
        db.commitTransaction();
        db.beginTransaction();
    }
   
    private static void addLoan(final Realm db, final User user, final Book book, Date from, Date to) {
        loanModel(db).addLoan(from, to, user.getId(), book.getId());
        checkpoint(db);
    }

    private static Book addBook(final Realm db, final String id, final String title) {
        Book book = bookModel(db).createOrUpdate(new Book(id, title));
        checkpoint(db);
        return book;
    }

    private static User addUser(final Realm db, final String id, final String name,
                                final String lastName, final int age) {
        User user = userModel(db).createOrUpdate(new User(id, name, lastName, age));
        checkpoint(db);
        return user;
    }

If you’re interested in taking the modified example for a spin, you can download the source code here.

Conclusion

It’s exciting to see some guidance and framework support from Google to make Android development cleaner and easier than ever. With first class support for Kotlin and more architecture components to come, (and faster reactive NOSQL alternatives to SQLite 😉), there is no better time than now to be an Android developer!

Next Up: Realm Java enables you to efficiently write your Android app’s model layer in a safe, persisted, and fast way.

General link arrow white

Eric Maxwell

Eric is an Product Engineer at Realm. He has spent over a decade architecting and developing software for various companies across different industries, including healthcare, insurance, library science, and private aviation. His current focus is training, mentoring, and mobile development. He has developed and taught courses on Java, Android and iOS. When he’s not working, he enjoys time with family, traveling and improv comedy.