More and more complicated

In my previous article I’ve shown you a simple example of how to use Realm database. However projects that we are working on are definitely more complicated. We use different libraries like ButterKnife, EventBus, Retrofit, Dagger and so on. We also try to make our app more testable i.e. with the use of MVP pattern. All these things should make our life easier, sure, but it’s not that obvious how to implement it correctly – especially for the first time. In below example I tried to use Realm database in app with MVP architecture and Dagger library.

Functionalities

What the app can do?

List all books. Add new book. Show book’s details. Show all publisher’s books. Show all author’s books.

Model

Model consists of three simple classes.

public class Book extends RealmObject { @PrimaryKey private int id; private String isbn; private String title; private Author author; private Publisher publisher; //getters and setters } 1 2 3 4 5 6 7 8 9 10 11 12 public class Book extends RealmObject { @ PrimaryKey private int id ; private String isbn ; private String title ; private Author author ; private Publisher publisher ; //getters and setters }

public class Author extends RealmObject { @PrimaryKey private int id; private String name; private String lastname; private RealmList books; //getters and setters } 1 2 3 4 5 6 7 8 9 10 11 public class Author extends RealmObject { @ PrimaryKey private int id ; private String name ; private String lastname ; private RealmList books ; //getters and setters }

public class Publisher extends RealmObject { @PrimaryKey private int id; private String name; private RealmList books; //getters and setters } 1 2 3 4 5 6 7 8 9 10 public class Publisher extends RealmObject { @ PrimaryKey private int id ; private String name ; private RealmList books ; //getters and setters }

Using Realm

According to the documentation:

This means that on the UI thread the easiest and safest approach is to open a Realm instance in all your Activities and Fragments and close it again when the Activity or Fragment is destroyed.

That’s what we’ll do with Dagger and MVP.

Dagger

I used Dagger, among others, to get Realm instance for each Activity. First of all Realm configuration was initialized in Application class in initRealmConfiguration() method.

public class BooksApplication extends Application { private static BooksApplication sInstance; private ObjectGraph mApplicationGraph; @Override public void onCreate() { super.onCreate(); sInstance = this; initRealmConfiguration(); initApplicationGraph(); } private void initRealmConfiguration() { RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this) .deleteRealmIfMigrationNeeded() .build(); Realm.setDefaultConfiguration(realmConfiguration); } private void initApplicationGraph() { mApplicationGraph = ObjectGraph.create(new ApplicationModule()); } public static void injectModules(@NonNull final Object object, final Object... modules) { sInstance.mApplicationGraph.plus(modules).inject(object); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class BooksApplication extends Application { private static BooksApplication sInstance ; private ObjectGraph mApplicationGraph ; @ Override public void onCreate ( ) { super . onCreate ( ) ; sInstance = this ; initRealmConfiguration ( ) ; initApplicationGraph ( ) ; } private void initRealmConfiguration ( ) { RealmConfiguration realmConfiguration = new RealmConfiguration . Builder ( this ) . deleteRealmIfMigrationNeeded ( ) . build ( ) ; Realm . setDefaultConfiguration ( realmConfiguration ) ; } private void initApplicationGraph ( ) { mApplicationGraph = ObjectGraph . create ( new ApplicationModule ( ) ) ; } public static void injectModules ( @ NonNull final Object object , final Object . . . modules ) { sInstance . mApplicationGraph . plus ( modules ) . inject ( object ) ; } }

Next thing is to provide Realm instance.

@Module(injects = BooksApplication.class, library = true) public class ApplicationModule { @Provides Realm provideRealm() { return Realm.getDefaultInstance(); } @Provides RealmService provideRealmService(final Realm realm) { return new RealmService(realm); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 @ Module ( injects = BooksApplication . class , library = true ) public class ApplicationModule { @ Provides Realm provideRealm ( ) { return Realm . getDefaultInstance ( ) ; } @ Provides RealmService provideRealmService ( final Realm realm ) { return new RealmService ( realm ) ; } }

Now we are ready to provide Realm instance to each Activity. ApplicationModule also provides RealmService object but about that later.

MVP

With the use of MVP pattern we try to make our views like Jon Snow – they should know nothing. Views should take care only to show something on screen or get taps and pass them on. Without ‘thinking’ and any logic. So views shouldn’t know about Realm instance either. We have to pass Realm instance to Presenter but dealing with database directly in it could be a huge burden. RealmService solves the problem.

The code of BooksActivity is shown below.

public class BooksActivity extends BaseActivity implements BooksView, BookListAdapter.OnBookClickListener { @Bind(R.id.recycler_view) RecyclerView mRecyclerView; @Bind(R.id.toolbar) Toolbar mToolbar; @Inject BooksPresenter mBooksPresenter; private BookListAdapter mAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); initToolbar(); initList(); } @Override protected Object getModule() { return new BooksModule(); } private void initToolbar() { setSupportActionBar(mToolbar); } private void initList() { mAdapter = new BookListAdapter(); mAdapter.setOnBookClickListener(this); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mRecyclerView.setAdapter(mAdapter); } @Override protected void onStart() { super.onStart(); mBooksPresenter.setView(this); } @Override protected void onStop() { super.onStop(); mBooksPresenter.clearView(); } @Override protected void closeRealm() { mBooksPresenter.closeRealm(); } @Override public void showBooks(final RealmResults books) { mAdapter.setBooks(books); } @Override public void onBookClick(final int id) { mBooksPresenter.onBookClick(id); } @OnClick(R.id.fab) public void onAddNewBookClick() { mBooksPresenter.onAddNewBookClick(); } @Override public void showBookDetailView(final int id) { startActivity(DetailActivity.getStartIntent(this, id)); } @Override public void showAddNewBookView() { startActivity(new Intent(this, AddBookActivity.class)); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 public class BooksActivity extends BaseActivity implements BooksView , BookListAdapter . OnBookClickListener { @ Bind ( R . id . recycler_view ) RecyclerView mRecyclerView ; @ Bind ( R . id . toolbar ) Toolbar mToolbar ; @ Inject BooksPresenter mBooksPresenter ; private BookListAdapter mAdapter ; @ Override public void onCreate ( Bundle savedInstanceState ) { super . onCreate ( savedInstanceState ) ; setContentView ( R . layout . activity_main ) ; ButterKnife . bind ( this ) ; initToolbar ( ) ; initList ( ) ; } @ Override protected Object getModule ( ) { return new BooksModule ( ) ; } private void initToolbar ( ) { setSupportActionBar ( mToolbar ) ; } private void initList ( ) { mAdapter = new BookListAdapter ( ) ; mAdapter . setOnBookClickListener ( this ) ; mRecyclerView . setLayoutManager ( new LinearLayoutManager ( this ) ) ; mRecyclerView . setAdapter ( mAdapter ) ; } @ Override protected void onStart ( ) { super . onStart ( ) ; mBooksPresenter . setView ( this ) ; } @ Override protected void onStop ( ) { super . onStop ( ) ; mBooksPresenter . clearView ( ) ; } @ Override protected void closeRealm ( ) { mBooksPresenter . closeRealm ( ) ; } @ Override public void showBooks ( final RealmResults books ) { mAdapter . setBooks ( books ) ; } @ Override public void onBookClick ( final int id ) { mBooksPresenter . onBookClick ( id ) ; } @ OnClick ( R . id . fab ) public void onAddNewBookClick ( ) { mBooksPresenter . onAddNewBookClick ( ) ; } @ Override public void showBookDetailView ( final int id ) { startActivity ( DetailActivity . getStartIntent ( this , id ) ) ; } @ Override public void showAddNewBookView ( ) { startActivity ( new Intent ( this , AddBookActivity . class ) ) ; } }

What that code is doing?

Initialize adapter and RecyclerView in initList().

Manage the View in presenter in onStart() and onStop().

Show books in showBooks(…).

Get taps in onBookClick(…) and onAddNewBookClick().

Go to another views in showBookDetailView(…) and showAddNewBookView().

For now don’t bother about Presenter injection and closing Realm instance. How Presenter and View interfaces look like?

public interface BooksPresenter extends BasePresenter { void onBookClick(int id); void onAddNewBookClick(); } 1 2 3 4 public interface BooksPresenter extends BasePresenter { void onBookClick ( int id ) ; void onAddNewBookClick ( ) ; }

public interface BasePresenter { void setView(Object view); void clearView(); void closeRealm(); } 1 2 3 4 5 public interface BasePresenter { void setView ( Object view ) ; void clearView ( ) ; void closeRealm ( ) ; }

public interface BooksView { void showBooks(RealmResults books); void showBookDetailView(int id); void showAddNewBookView(); class EmptyMyListView implements BooksView { @Override public void showBooks(final RealmResults books) { } @Override public void showBookDetailView(final int id) { } @Override public void showAddNewBookView() { } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public interface BooksView { void showBooks ( RealmResults books ) ; void showBookDetailView ( int id ) ; void showAddNewBookView ( ) ; class EmptyMyListView implements BooksView { @ Override public void showBooks ( final RealmResults books ) { } @ Override public void showBookDetailView ( final int id ) { } @ Override public void showAddNewBookView ( ) { } } }

The BooksActivity implements BooksView and implementation of BooksPresenter is shown below.

public class BooksPresenterImpl implements BooksPresenter { private final RealmService mRealmService; private BooksView mMyListView = new BooksView.EmptyMyListView(); private boolean booksWereShown = false; public BooksPresenterImpl(final RealmService realmService) { mRealmService = realmService; } @Override public void setView(final Object view) { mMyListView = (BooksView) view; showBooksIfNeeded(); } private void showBooksIfNeeded() { if(!booksWereShown) { mMyListView.showBooks(mRealmService.getAllBooks()); booksWereShown = true; } } @Override public void clearView() { mMyListView = new BooksView.EmptyMyListView(); } @Override public void closeRealm() { mRealmService.closeRealm(); } @Override public void onBookClick(final int id) { mMyListView.showBookDetailView(id); } @Override public void onAddNewBookClick() { mMyListView.showAddNewBookView(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class BooksPresenterImpl implements BooksPresenter { private final RealmService mRealmService ; private BooksView mMyListView = new BooksView . EmptyMyListView ( ) ; private boolean booksWereShown = false ; public BooksPresenterImpl ( final RealmService realmService ) { mRealmService = realmService ; } @ Override public void setView ( final Object view ) { mMyListView = ( BooksView ) view ; showBooksIfNeeded ( ) ; } private void showBooksIfNeeded ( ) { if ( ! booksWereShown ) { mMyListView . showBooks ( mRealmService . getAllBooks ( ) ) ; booksWereShown = true ; } } @ Override public void clearView ( ) { mMyListView = new BooksView . EmptyMyListView ( ) ; } @ Override public void closeRealm ( ) { mRealmService . closeRealm ( ) ; } @ Override public void onBookClick ( final int id ) { mMyListView . showBookDetailView ( id ) ; } @ Override public void onAddNewBookClick ( ) { mMyListView . showAddNewBookView ( ) ; } }

As you see, Presenter handles the clicks and shows all added books using RealmService. Let’s do a quick look at the BaseActivity and than we go to RealmService.

Injection and close Realm

The BaseActivity is responsible for injecting modules and forcing inheriting Activities to implement closeRealm() method.

public abstract class BaseActivity extends AppCompatActivity { @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); BooksApplication.injectModules(this, getModule()); } @Override protected void onDestroy() { closeRealm(); super.onDestroy(); } protected abstract Object getModule(); protected abstract void closeRealm(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public abstract class BaseActivity extends AppCompatActivity { @ Override public void onCreate ( final Bundle savedInstanceState ) { super . onCreate ( savedInstanceState ) ; BooksApplication . injectModules ( this , getModule ( ) ) ; } @ Override protected void onDestroy ( ) { closeRealm ( ) ; super . onDestroy ( ) ; } protected abstract Object getModule ( ) ; protected abstract void closeRealm ( ) ; }

Now we open Realm instance in onCreate() and close it in onDestroy() – as it was said in documentation.

@Module(injects = BooksActivity.class, addsTo = ApplicationModule.class) public class BooksModule { @Provides BooksPresenter provideMyListPresenter(final RealmService realmService) { return new BooksPresenterImpl(realmService); } } 1 2 3 4 5 6 7 8 @ Module ( injects = BooksActivity . class , addsTo = ApplicationModule . class ) public class BooksModule { @ Provides BooksPresenter provideMyListPresenter ( final RealmService realmService ) { return new BooksPresenterImpl ( realmService ) ; } }

BooksModule simply injects BooksPresenter with RealmService to the BooksActivity.

RealmService

As long as we do read queries we are allowed to do them on UI thread and use injected Realm instance field.

public class RealmService { private final Realm mRealm; public RealmService(final Realm realm) { mRealm = realm; } public void closeRealm() { mRealm.close(); } public RealmResults getAllBooks() { return mRealm.allObjects(Book.class); } //other methods } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class RealmService { private final Realm mRealm ; public RealmService ( final Realm realm ) { mRealm = realm ; } public void closeRealm ( ) { mRealm . close ( ) ; } public RealmResults getAllBooks ( ) { return mRealm . allObjects ( Book . class ) ; } //other methods }

The problem occurs with database modifications like write or update. If write operation is not a simple one we should do it on background thread.

We can either use an AsyncTask and in doInBackground() make write operation with new Realm instance between Realm.beginTransaction() and Realm.commitTransaction() or use method Realm.executeTransaction(…).

The first idea is not a good one for our architecture. All database operation should be doing outside the View, so the AsyncTask should be implemented in Presenter or some kind of service, but we also don’t want Android framework in any classes except Views.

Therefore let’s try the second solution.

public class RealmService { //other methods public void addBookAsync(final String title, final String author, final String isbn, final String publisher, final OnTransactionCallback onTransactionCallback) { mRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(final Realm realm) { Book book = realm.createObject(Book.class); book.setId(realm.allObjects(Book.class).size()); book.setTitle(title); book.setAuthor(createOrGetAuthor(author, book, realm)); book.setPublisher(createOrGetPublisher(publisher, book, realm)); book.setIsbn(isbn); } }, new Realm.Transaction.Callback() { @Override public void onSuccess() { if (onTransactionCallback != null) { onTransactionCallback.onRealmSuccess(); } } @Override public void onError(final Exception e) { if (onTransactionCallback != null) { onTransactionCallback.onRealmError(e); } } }); } //other methods public interface OnTransactionCallback { void onRealmSuccess(); void onRealmError(final Exception e); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class RealmService { //other methods public void addBookAsync ( final String title , final String author , final String isbn , final String publisher , final OnTransactionCallback onTransactionCallback ) { mRealm . executeTransaction ( new Realm . Transaction ( ) { @ Override public void execute ( final Realm realm ) { Book book = realm . createObject ( Book . class ) ; book . setId ( realm . allObjects ( Book . class ) . size ( ) ) ; book . setTitle ( title ) ; book . setAuthor ( createOrGetAuthor ( author , book , realm ) ) ; book . setPublisher ( createOrGetPublisher ( publisher , book , realm ) ) ; book . setIsbn ( isbn ) ; } } , new Realm . Transaction . Callback ( ) { @ Override public void onSuccess ( ) { if ( onTransactionCallback ! = null ) { onTransactionCallback . onRealmSuccess ( ) ; } } @ Override public void onError ( final Exception e ) { if ( onTransactionCallback ! = null ) { onTransactionCallback . onRealmError ( e ) ; } } } ) ; } //other methods public interface OnTransactionCallback { void onRealmSuccess ( ) ; void onRealmError ( final Exception e ) ; } }

Method execute() is called in background thread. If there was no exception during operations on database then onSucces() method is called, if not – onError(…). Notice that in execute() body the new instance of Realm is used.

Summary

It is possible to use Realm in MVP architecture. Even if we can’t use Realm instance in different thread than it was created, we can use async transactions mechanizm with callbacks which Realm provides. Opening and closing Realm instance in each Activity and Fragment is not a problem as long as we use Dagger and some base component which views have to inherit.

Do you have any other ideas how to use Realm with MVP? Feel free to comment.