Example of Realm with MVP and Dagger
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.
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 } |
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 } |
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.
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.
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.
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?
1 2 3 4 |
public interface BooksPresenter extends BasePresenter { void onBookClick(int id); void onAddNewBookClick(); } |
1 2 3 4 5 |
public interface BasePresenter { void setView(Object view); void clearView(); void closeRealm(); } |
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.
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.
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.
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.
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.
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.
The project can be found on DroidsOnRoids Github in ExampleRealmMVP-Android repository.
Ready to take your business to the next level with a digital product?
We'll be with you every step of the way, from idea to launch and beyond!
thanks @Mateusz for this nice tutorial!
great work
but why you still using dagger 1, i think that dagger 2 is more flexible and easy to setup …
Thanks! Yeah, I know… I haven’t used dagger 2 yet so I decided to write this example with dagger 1. But I hope that despite this the example is still helpful.
What about using RxJava instead of AsyncTask? It’s more flexible and consice.
Also is there the real need to create Presenters’ interfaces? One more file of code with no real need (I can’t image the case where it will be useful)
You mentions Retrofit. So the models you created will be instatiated by API layer? If it’s so, than why is it permissible to use something like “private RealmList books;”? In my work every api returns id/ids, not fully-fledged objects (all my api returns json with depth 1)
Thanks for comment!
Of course you can use RxJava but I didn’t use it in purpose. I just didn’t want to make the example unclear because of RxJava and wanted to talk about Realm with RxJava in the next article.
Presenters’ interfaces are useful if you want to have a few implementations of the Presenter. Thanks to that you can switch the behavior in an easy way i.e. with the use of factory design pattern.
If you use RxJava, the Json with ids it’s not a problem. You can make the request to API and retrieve needed data from database and pass them to adapter as RealmList. You can also talk with API team about the structure of Json response and make it like your model. I usually work with APIs that return not ids but full objects and I guess that Gson library (which Retrofit also uses) was created for this reason. It is very convenient especially if you implement the ‘API browser’ type of app which mostly just shows the data from API.
Hey @mateusz_budzar:disqus ! I loved the idea. Just have one doubt? Don’t we have to call close on Realm? At least, according their guidelines we should call it.
Hey @fbiocarballo:disqus ! Thanks.
Yes, you are right and in above example I also call close() method on Realm in RealmService class.
Ah now I see it. However, I see you closing it when an activity/presenter is destroyed, but I don’t see you “opening” it or getting a new instance. After closing won’t be that Relam instance closed? As it is a Singleton through the use of Dagger?
Through the Dagger I’m not passing a Singleton. There is no Singleton annotation in ApplicationModule so after close you will get a new instance of Realm.
Ah, great!
Do you know if there is anything bad in having a singleton realm instance through the lifecycle of the app? Following the idea of having a Realm instance provided with the @Singleton annotation.
You cannot provide Realm instance as singleton in application module, because when you close that instance in one activity you won’t be able to use it in another. You will then inject close instance and get an exception.
As far as I see this approach has problems with passing RealmResults to View.
View depends on Realm and it’s bad because
1. it makes testing harder
2. when we’ll remove Realm dependency (in order to use another ORM) we’ll need not only revrite code inside Model also it’ll highline View’s code
Also View can modify databse via RealmResults.
I may be wrong so correct me in this case please.