Zhuinden / realm-book-example

This is an example rewrite of AndroidHive's messy tutorial, accompanying the following article on Realm.
https://medium.com/@Zhuinden/how-to-use-realm-for-android-like-a-champ-and-how-to-tell-if-youre-doing-it-wrong-ac4f66b7f149
Other
80 stars 19 forks source link

create BookDao class, in order to separate Db operations from presentation stuff #10

Closed aledesc closed 7 years ago

aledesc commented 7 years ago

Hi, this is not and issue, but an enhancement. I have reworked this a little bit, I'm new to Android, so I have been trying to find common ground with middle-ware architectures. I'm aware this is just an example, but, even in that case I send this to you, because think separation of concerns is a good thing!

Then I created the class :

public class BookDao { private Realm realm;

public BookDao()
{
    realm = RealmManager.getRealm();
}

private Book getNewInstance(long id, Book book)
{
    Book b = new Book();
    b.setId(id);
    b.setAuthor(book.getAuthor());
    b.setTitle(book.getTitle());
    return b;
}

private void updateInstance(Book newInst, Book oldInst)
{
    newInst.setAuthor( oldInst.getAuthor() );
    newInst.setTitle( oldInst.getTitle() );
    newInst.setDescription( oldInst.getDescription() );
    newInst.setImageUrl( oldInst.getImageUrl() );
}

public void create(final Book book)
{
    realm.executeTransactionAsync(new Realm.Transaction() {
        @Override
        public void execute(Realm realm)
        {
            long id = 1;
            if(realm.where(Book.class).count() > 0) {
                id = realm.where(Book.class).max(BookFields.ID).longValue() + 1; // auto-increment id
            }
            Book b = getNewInstance(id,book);
            realm.insertOrUpdate(b);
        }
    });
}

public Book read(long id)
{
    return realm.where(Book.class).equalTo(BookFields.ID, id).findFirst();
}

public void update(final Book book)
{
    realm.executeTransactionAsync(new Realm.Transaction() {
        @Override
        public void execute(Realm realm) {
        Book b = realm.where(Book.class).equalTo(BookFields.ID, book.getId()).findFirst();
        if(b != null)
        {
            updateInstance(b, book);
        }
        }
    });
}

public void delete(long i)
{
    realm.executeTransactionAsync(new Realm.Transaction() {
        @Override
        public void execute(Realm realm) {
        Book book = realm.where(Book.class).equalTo(BookFields.ID, id).findFirst();
        if(book != null) {
            book.deleteFromRealm();
        }
        }
    });
}

}

Delegating realm stuff to this class, Book presenter operations look like these :

private Book getBookFromDC(ViewContract.DialogContract dialogContract,long id)
{
    Book b = new Book();
    b.setId(id);
    b.setAuthor(dialogContract.getAuthor());
    b.setImageUrl(dialogContract.getThumbnail());
    b.setTitle(dialogContract.getTitle());

    return b;
}

public void saveBook(ViewContract.DialogContract dialogContract)
{
    if(hasView())
    {
        String title = dialogContract.getTitle();

        if(title == null || "".equals(title.trim())) {
            viewContract.showMissingTitle();
        }
        else
        {
            BookDao bookDao = new BookDao();
            bookDao.create( getBookFromDC(dialogContract,0));
        }
    }
}

public void deleteBookById(final long id)
{
    BookDao bookDao = new BookDao();
    bookDao.delete(id);
}

public void editBook(final ViewContract.DialogContract dialogContract, final long id)
{
    BookDao bookDao = new BookDao();
    bookDao.update(getBookFromDC(dialogContract, id));
}
Zhuinden commented 7 years ago
public BookDao()
{
    realm = RealmManager.getRealm();
}

This is a bad idea because Realm instances are thread-confined, so while in this current example it would work, but if I had any operations on any background thread, I'd crash out with an IllegalStateException: realm access from incorrect thread.

So I'd have to pass the Realm instance into the DAO methods to handle Realm lifecycle externally.

And also BookDao bookDao = new BookDao() not a very good practice, that's what you'd use Dagger2 for (dependency injection, @Inject annotation).

I think I specifically didn't add Dagger into this example because I wanted to keep it simple, though.

aledesc commented 7 years ago

Fine, those are the intricacies I'm not aware of. Neither have I used Dagger, always the Spring DI mechanism. Anyway, good points, but to keep it simple without DI, constructor hast to be used, or get the instance from a factory.

In any case, my point was mainly to add the separation of concerns, to make the example easier to understand for me. Maybe it gets to be easier for others too.

Zhuinden commented 7 years ago

Due to the nature of Realm, separation of concerns by layer is actually surprisingly difficult. For example, RealmRecyclerViewAdapter class clearly belongs in presentation layer, but requires realm-specific collections.

To hide Realm inside the data layer, you need to give up some features Realm provides, namely the lazy-loaded auto-updating proxy objects. And that is out of scope for this example. I have an example for that here.

The original code for this realm-book-example is http://www.androidhive.info/2016/05/android-working-with-realm-database-replacing-sqlite-core-data/, the presenter was added to create at least some semblance of reasonable structure. I didn't feel that adding additional BookRepository class would have improved it for a beginner.

But by default if I wanted to unit test the presenter, I agree, I'd have to do a bit more ripping apart; but that would make the example harder to follow for beginners.