Click here to Skip to main content
14,216,534 members
Click here to Skip to main content
Technical Blog
Posted 27 Jun 2016

Tagged as

Stats

14.6K views
1 bookmarked

Realm Database Tutorial For Android – Part 1

,
Rate this:
5.00 (6 votes)
Please Sign up or sign in to vote.
5.00 (6 votes)
27 Jun 2016     CPOL    
Realm Database Tutorial For Android – Part 1

Are you using Realm database yet for your Android projects? If not, it's possible you never heard of Realm. Well, I have just recently used Realm database for three Android development projects and in this post, I will share with you my summary of lessons learned, tips and best practices of working with Realm database for Android. In the next post, I will share with you a step by step tutorial on how to create a CRUD app with Realm along with the source code. For now, get your pen and paper and be prepared to take note – Enjoy.

What is Realm Database in-case you are just encountering the term? According to the official Realm description, Realm is a mobile first database that is built from the ground-up to run directly inside phones, tablets and wearable. Realm is a “better database: faster, simpler, & Java-native”. These words “better, faster and simpler” implies a comparison – and the comparison here is to SQLite for Android. While SQLite will continue to have its place in Android development, I believe that Realm saves a lot of developer time, that is my experience after recently implementing Realm in three Android projects.

How Does Realm Database Work?

Realm database does not run on top of SQLite, it is not an ORM, it is a Realm. Quoting the official Realm documentation, “Realms are our equivalent of a database: they contain different kinds of objects, and map to one file on disk”.

For example, let us consider a Point of Sale app that among other things contains Products for sale. With SQLite, here is how you may save this Product to database.

First, you create the model class like this:

public class Product{  
    private long id;
    private String productName;
    private String description;
    private String promoMessage;
    private double salePrice;
    private double purchasePrice;
    private String imagePath;
    private long categoryId;
    private long dateAdded;
    private long dateOfLastTransaction;
}

Then, you create a table like this to represent the Product object.

//String to create a product table
    private static final String CREATE_PRODUCT_TABLE =
            "CREATE TABLE " + Constants.PRODUCT_TABLE + "("
                    + Constants.COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
                    + Constants.COLUMN_NAME + " TEXT NOT NULL, "
                    + Constants.COLUMN_DESCRIPTION + " TEXT, "
                    + Constants.COLUMN_PROMO_MESSAGE + " TEXT, "
                    + Constants.COLUMN_PRICE + " NUMERIC, "
                    + Constants.COLUMN_PURCHASE_PRICE + " NUMERIC, "
                    + Constants.COLUMN_IMAGE_PATH + " TEXT, "
                    + Constants.COLUMN_CATEGORY_ID + " INTEGER, "
                    + Constants.COLUMN_CATEGORY_NAME + " TEXT, "
                    + Constants.COLUMN_DATE_CREATED + " BIGINT, "
                    + Constants.COLUMN_LAST_UPDATED + " BIGINT, "
                    + "FOREIGN KEY(category_id) REFERENCES category(_id)" + ")";

Once you define the table, you will now have to manage the conversion between the Java object and the data representation that is written to disk. Tools like the Cursor, SQLiteOpenHelper classes helps, however a lot of developer mental muscle is used to track the state of the objects as they are persisted and read from disk, not to mention the increased opportunities for typos. Here is how you may have to read the data saved from Product table back to a Product object.

public static Product getProductFromCursor(Cursor cursor) {
        long id = cursor.getLong(cursor.getColumnIndex(Constants.COLUMN_ID));
        String name = cursor.getString(cursor.getColumnIndex(Constants.COLUMN_NAME));
        String description = cursor.getString(cursor.getColumnIndex(Constants.COLUMN_DESCRIPTION));
        String promoMessage = cursor.getString(cursor.getColumnIndex(Constants.COLUMN_PROMO_MESSAGE));
        Double salePrice = cursor.getDouble(cursor.getColumnIndex(Constants.COLUMN_PRICE));
        Double purchasePrice = cursor.getDouble(cursor.getColumnIndex(Constants.COLUMN_PURCHASE_PRICE));
        String imagePath = cursor.getString(cursor.getColumnIndex(Constants.COLUMN_IMAGE_PATH));
        long catId = cursor.getLong(cursor.getColumnIndex(Constants.COLUMN_CATEGORY_ID));
        String catName = cursor.getString(cursor.getColumnIndex(Constants.COLUMN_CATEGORY_NAME));
        long dateCreated = cursor.getLong(cursor.getColumnIndex(Constants.COLUMN_DATE_CREATED));
        long dateLastUpdated = cursor.getLong(cursor.getColumnIndex(Constants.COLUMN_LAST_UPDATED));

        Product product = new Product();
        product.setId(id);
        product.setProductName(name);
        product.setDescription(description);
        product.setPromoMessage(promoMessage);
        product.setSalePrice(salePrice);
        product.setPurchasePrice(purchasePrice);
        product.setImagePath(imagePath);
        product.setCategoryId(catId);
        product.setCategoryName(catName);
        product.setDateAdded(dateCreated);
        product.setDateOfLastTransaction(dateLastUpdated);
        return product;
    }

All of that, just to save and retrieve an object to database.

With Realm, all the above code can be replaced like this:

public class Product extends RealmObject {
    @PrimaryKey
    private long id;

    private String productName;
    private String description;
    private String promoMessage;
    private double salePrice;
    private double purchasePrice;
    private String imagePath;
    private long categoryId;
    private String categoryName;
    private long dateAdded;
    private long dateOfLastTransaction;

    public Product(){
    }
}

Just by extending the class from RealmObject makes that class a persist-able object – a Realm. And here is how you save a Product object to Realm database asynchronously.

public void addProduct(final Product product) {
        mRealm.executeTransactionAsync(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                long id = primaryKey.incrementAndGet();
                product.setId(id);
                realm.copyToRealm(product);
            }
        });
    }

And here is how you get that Product back:

public Product getProductById(long id) {
       Product product = mRealm.where(Product.class).equalTo("id", id).findFirst();
        return product;
    }

That is it! Very straight forward and lets you get back to writing business logic instead of wrestling with data persistence code. This does not mean that Realm does not have its growth opportunities. So before we dive into code to create the demo project, let me share some challenges that you need to be aware off in regards to working with Realm database, and some recommended workaround.

Challenges Working with Realm Database for Android

Auto Increment Primary Key

Realm does not currently support auto increment of Primary Key.

Workaround

There are two workarounds regarding auto increment of primary key.

  1. Query for the max primary key value each time you want to insert data and then increment that by 1 and set as the primary key.
    long id = realm.where(Product.class).max("id").longValue();
                    product.setId(id);
  2. Run the query to find the Id once, probably when the app launches and then keep a reference to that max id value and then increment and get that value as needed like so:
    AtomicLong productPrimaryKey = new AtomicLong(realm.where(Product.class).max("id").longValue() + 1);
                    long id = productPrimaryKey.getAndIncrement();
                    product.setId(id);

Inheritance/Polymorphism

Realm does not currently support inheritance – aka IS-A relationship. For example, to sell a Product to a Customer, you need to specify the quantity. Since quantity is a value that is unique to each transaction, you may create a subclass of Product called LineItem where you define the quantity like this:
public class LineItem extends Product {
    private int quantity;

    public LineItem() {
    }

    public LineItem(Product product, int quantity) {
        super(product);
        this.quantity = quantity;
    }   
}

This will not work with Realm for now, check this Github issue for updates if your project is absolutely dependent on polymorphic inheritance.

Workaround

The workaround for inheritance with Realm database is composition aka HAS-A relationship. With this, the above LineItem object can be re-written as:

public class LineItem extends RealmObject {

    @PrimaryKey
    public long id;
    public int quantity;
    public Product product;

    public LineItem() {
    }

    public LineItem(Product product) {
       this.quantity = 1;
        this.product = product;
    }
}

Relationships

Relationships in Realm has to be set in both ends as they are not bi-directional by default. Take for example a Sale Transaction object that contains a list of LineItems. Here is what the Transaction model class may look like:

public class SalesTransaction extends RealmObject{
    @PrimaryKey
    public long id;

    public double subTotalAmount;
    public double taxAmount;
    public double totalAmount;
    public boolean paid;
    public long transactionDate;

    public Customer customer;

    public SalesTransaction(){
        lineItems = new RealmList<>();
    }

    public RealmList<LineItem> lineItems;
}

You may be tempted to think that the one to many relationship between the Transaction and LineItems is automatically updated but it is not, so you have to set both ends when you save the object like so:

public void saveTransactionAsync(final SalesTransaction salesTransaction, 
  final List&lt;LineItem&gt; items) {
    mRealm.executeTransactionAsync(new Realm.Transaction() {
        @Override
        public void execute(Realm realm) {
            realm.copyToRealmOrUpdate(salesTransaction);
            for (LineItem item: items){
                long id = lineItemPrimaryKey.incrementAndGet();
                item.id = id;
                item.salesTransaction = salesTransaction;  //set the One to Many end of the Relationship
                mRealm.copyToRealmOrUpdate(item);
                salesTransaction.lineItems.add(item);  //set the Many to One end of the Relationship
            }
        }
    });
}

Closing Realm Database

It is written, thou shall remember to close Realm instances when you’re done with it in order not to leak memory! That makes sense. And the documentation goes on to say that.

This means that on the UI thread is 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.

https://realm.io/docs/java/latest/#controlling-the-lifecycle-of-realm-instances

That sounds good except when you want to use Realm outside of the Activity/Fragment say for example in an MVP pattern, since POCO classes do not have onPause() and onDestroy() callback methods.

Workaround

There are two workarounds on how to open and close Realm instances outside of an Activity/Fragment.

  1. Dependency Injection – One option is to use Dagger 2 to inject Realm into the classes where you need Realm database. You can annotate your Realm @Provides with @Singleton this way only one instance of Realm is created. The challenge with this approach is that your app may still be holding a reference to Realm when it is back-grounded which will make it a target for garbage collecting if Android runs short on resources.
  2. Accept Realm as a Parameter – Alternatively you can simply accept Realm as a constructor parameter in the classes where Realm database is needed. That is the approach that I use in the sample source code for this tutorial.

Threading

Consider this statement from Realm documentation:

The only limitation is that you cannot randomly pass Realm objects between threads. If you need the same data on another thread, you just need to query for that data on that other thread.

https://realm.io/docs/java/latest/#threading

The challenge with this advice is that elsewhere in the documentation, it is rightly suggested that you can do your database reads on the Main thread but do your writes on the background thread. So if you retrieve a product object like this:

public Product getProductById(long id) {
 RealmResults<Product> results = mRealm.where(Product.class).equalTo("id", id).findAll();
 Product result = results.first();
 return result;
 }

And then you update this record in your Fragment or Presenter, if you now go back to persist the changes back to the database like this:

public void updateProductAsync(final Product product) {

        mRealm.executeTransactionAsync(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                realm.copyToRealmOrUpdate(product);
            }        
        });
    }

This will fail, because, you got the object from another thread, modified it in that thread and now you want to save it in another thread.

Workaround

The workaround for this is to actually follow what the documentation said by not passing an object from one thread to the other. This means that in some cases you may have to do your write transactions in the main thread.

Alternatively, you can detach your objects from Realm when you fetch them from Realm database by calling copyFromRealm like this:

RealmResults<Product> products = mRealm.where(Product.class).findAll();
        List<Product> result = mRealm.copyFromRealm(products);
        return result;

This way, you can update your object outside of Realm and when you are ready to save it back to Realm database, you simply call copyToRealmOrUpdate inside a Realm transaction. Please note that the above approach will not work if your object contains another Realm object, that child object will not be detached when you call copyFromRealm on the parent.

Realm Database Demo Project

If I have not managed to scare you off from Realm database, then great. Let us see Realm in action by creating a feature of a Shopping class app. This app displays a list of products, allows us to add, edit or delete the product.

Continued in the next post to be published very soon.

The post Realm Database Tutorial For Android – Part 1 appeared first on Val Okafor.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Val Okafor
Software Developer (Senior) ValOkafor.com
United States United States
My name is Val Okafor, I am a Senior Software Engineer with specialization in Android Development. Learning and problem solving is my passion and I share my Android development knowledge through my blog ValOkafor.com.

My Android courses are the courses I wish I had when I started. I teach Android development in the context of a fully developed Android app. I believe that new Android concepts are better understood if they are presented in the context of creating an app from scratch to finish.

I focus on creating Productivity Android apps and besides Android development I have 7 years’ experience as System Administrator supporting Enterprise applications and 2 years’ experience as a Web Developer building websites using PHP and ASP.Net.

I have worked for corporations such as The Home Depot, American Council on Exercise, Legend3D and HD Supply, Inc. I have a bachelor's degree in Information Technology from National University San Diego, California and a master's degree in Software Engineering from Regis University Denver, Colorado.

I enjoy sharing my extensive work experience through my blog, social media.

Comments and Discussions

 
-- There are no messages in this forum --