Room Library

Android Room using Java

home office, programming, programmer

Android Room tutorial using Java will guide you to quickly perform the database integration using Room library without having any prior background or understanding about it. But, you must have some experience with Android Java or Android Kotlin.

Room is a library built on top of Android SQLite to ease out the database integration. Room library offers more control on your local database system over your native SQLiteOpenHelper. It has a nice cache feature for your application’s data. 

Being a software developer, I sometimes come across questions. 

Why can’t I stick to something which I already know? Why does new stuff keep coming and whatever I know becomes old fashioned in software industry.

Is there anything I can do to keep myself up-to-date or this exercise is not required at all. 

When I could see, the same old fashioned, SQLiteOpenHelper working for me. May be this only comes because I don’t want to come out of my comfort zone. In contrast to that, another statement has always resulted with great experience and more satisfaction. So, I stick to the contrast.

If you are looking for the database integration using Kotlin, please go through this blog post ANDROID ROOM USING KOTLIN.

In this tutorial we will explore Android Room with a View JAVA

We will not cover all the basics here, rather my intention would be to bring you into pace, so that you learn while building your live project, rather than going through all the details.

By the end of this tutorial, you should be able to do all the ADD, MOD, DEL, GET operations with your database using Android Room in java.

Let’s begin…

To use Android Room Library with Java you need to do following tasks, doesn’t matter whether you like it or not.

These are exactly the same steps even if you are looking to build it with Kotlin, only thing which will change is your syntax.

Entity Classes

TASK 1 : Entity Class is the mapping class of your SQLite Table structure. It should contain the respective key references, example :@PrimaryKey@ForeignKey@Ignore etc.

If you have fields in the structure which should not be saved into your database, simply add @Ignore on that field.

DAO Classes

TASK 2 : DAO Class is used to interact with your database. It contains methods which are the abstract reference to your database. This is the place where you create abstract methods such as : AddData, ModifyData, DeleteData, GetSingleData, GetList, etc. 

Rather than using SQL queries directly, you annotate or pre-define those.

RoomDatabase Class

TASK 3 : RoomDatabase Class is used to define your Database Connection. It must be an abstract class extended from RoomDatabase class.This is a layer on top of SQLite open helper.

Repository Classes

TASK 4 : Repository Class is the helper class which is not mandatory but to have this is a good programming practice. With this you can implement caching scenarios such as : Whether to fetch data directly from your server or to simply pull the data from existing local database.

This is a high level picture of what needs to be done. Next, we will clarify these statement using source code.

All these steps needs to be repeated, depending upon the number of tables you have. Bigger the data structure you hold, more will be the work required to create these database interfaces.

I am not asking you to keep your data structure small and neither scare you.

But, if there could be an alternative to all of this, wouldn’t you like it. I will come back to this, first let’s go through these tasks step by step.

Room Library Entity Class

Give a brief look at the source code dumped below. This is how your entity class is going to look like. 

You may have realized, this class primarily contains, the name of the table and all the fields which you hold for SQLiteDatabase of the given table.

Not to mention this, you must have all these entity classes created for all the tables you hold.

package com.example.database2;

import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity(
    tableName = (DatabaseConstants.AccountTableKey.TABLE_NAME))

public class Account {

	@PrimaryKey(autoGenerate = true)
	@ColumnInfo(name = DatabaseConstants.AccountTableKey.ACCOUNT_ID_FIELD)
	Long accountid;

	@ColumnInfo(name = DatabaseConstants.AccountTableKey.NAME_FIELD) public String name;

	@ColumnInfo(name = DatabaseConstants.AccountTableKey.AGE_FIELD) public Integer age;

	@ColumnInfo(name = DatabaseConstants.AccountTableKey.DATE_FIELD) public Long date;

	@ColumnInfo(name = DatabaseConstants.AccountTableKey.EARNINGS_FIELD) public Double earnings;

	@ColumnInfo(name = DatabaseConstants.AccountTableKey.MORE_FIELD) public Float more;

	public Account() { 

		name = null;
		age = null;
		date = null;
		earnings = null;
		more = null;
	}
}


Room Library DAO Class

DAO Interface is utilized by Android Room Library to create API for the code. You define these methods ahead of time. 

Please check the getAllData() method defined below, you may observe there are two different methods with same name with different inputs.

@Query annotation has been used to specify the query used with the method, where as @RawQuery annotation can be used to simply pass the query with the method.

package com.example.database2;

import java.util.List;

import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.RawQuery;
import androidx.sqlite.db.SupportSQLiteQuery;

@Dao
public interface AccountDao extends BaseCommonDao {

    @Query("SELECT * FROM " + DatabaseConstants.AccountTableKey.TABLE_NAME)
	LiveData getAllData();

    @Query("SELECT * FROM " + DatabaseConstants.AccountTableKey.TABLE_NAME + " ORDER BY :orderBy") 
	LiveData getAllData(String orderBy);

    @RawQuery(observedEntities = Account.class)
    LiveData getData(SupportSQLiteQuery query);

    @RawQuery(observedEntities = Account.class)
    LiveData getListData(SupportSQLiteQuery query);

    @Query("DELETE FROM " + DatabaseConstants.AccountTableKey.TABLE_NAME)
    int deleteAllData();

    @RawQuery(observedEntities = Account.class)
    int delete(SupportSQLiteQuery query);

    /**
     * Table name to be utilized by BaseCommonDao to perform operations sa. execution of
     * Sqlite functions (MAX, MIN, etc..)
     */
    default String getTableName() {
        return DatabaseConstants.AccountTableKey.TABLE_NAME;
    }
}


Room Library Repository Class

Repository class provides clean abstraction to your DAO class. 

Please check the getData() method defined in the source code below. You may observe that the input is a ‘simple where condition’ without the need to know the SimpleSQLiteQuery class, but internally it is using the same getData() method of DAO class.

It would also help avoid redundancy of writing the same code again and again.

package com.example.database2;

import java.util.List;
import androidx.lifecycle.LiveData;
import androidx.sqlite.db.SimpleSQLiteQuery;

public class AccountRepository {

    private AccountDao accountDao;

    public AccountRepository(AccountDao accountDao) {

		this.accountDao = accountDao;
	}

    public LiveData getAllData() {

        return accountDao.getAllData();
    }

    public LiveData getAllData(String orderBy) {

        return accountDao.getAllData(orderBy);
    }

	public List insertAll(List data) {
        return accountDao.insertAll(data);
    }

	public Long insert(Account data) {
        return accountDao.insert(data);
    }

    public LiveData getData(String whereCondition) {

		StringBuilder finalQuery = new StringBuilder();
		finalQuery.append("SELECT * FROM " + DatabaseConstants.AccountTableKey.TABLE_NAME);

		if(whereCondition != null && whereCondition.length() > 0) {

			finalQuery.append(" WHERE " + whereCondition);
		}

		finalQuery.append(" LIMIT 1");

		SimpleSQLiteQuery simpleSQLiteQuery = new SimpleSQLiteQuery(finalQuery.toString());

		return accountDao.getData(simpleSQLiteQuery);
	}

	public LiveData getListData(String whereCondition, String orderBy) {

        StringBuilder finalQuery = new StringBuilder ();
        finalQuery.append("SELECT * FROM " + DatabaseConstants.AccountTableKey.TABLE_NAME);

		if(whereCondition != null && whereCondition.length() > 0) {

			finalQuery.append(" WHERE " + whereCondition);
		}

		if(orderBy != null && orderBy.length() > 0) {

			finalQuery.append(" ORDER BY " + orderBy);
		}
        SimpleSQLiteQuery simpleSQLiteQuery = new SimpleSQLiteQuery(finalQuery.toString());

        return accountDao.getListData(simpleSQLiteQuery);
    }

    public int delete(String whereCondition) {

		if(whereCondition == null || whereCondition.length() <= 0) {

			return 0;
		}

        String finalQuery = "DELETE FROM " + DatabaseConstants.AccountTableKey.TABLE_NAME  + " WHERE " + whereCondition;
        
        SimpleSQLiteQuery simpleSQLiteQuery = new SimpleSQLiteQuery(finalQuery);

        return accountDao.delete(simpleSQLiteQuery);
    }

    public int deleteAllData() {

        return accountDao.deleteAllData();
    }
}

RoomDatabase Class

This is a single ton class which holds only one open database connection throughout your application.

You need to specify all the DAO interfaces within this class as shown below, where “Account” is the database name.

package com.example.database2;


import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;


@Database(entities = {Account.class, Artist.class, Instrument.class}, version = 1, exportSchema = false)
//@TypeConverters(Converters.class)

abstract public class AppRoomDatabase extends RoomDatabase {

	abstract public AccountDao accountDao();
	abstract public ArtistDao artistDao();
	abstract public InstrumentDao instrumentDao();

	private static volatile AppRoomDatabase INSTANCE;

	public static AppRoomDatabase getDatabase(final Context context) {

        if (INSTANCE == null) {
            synchronized (AppRoomDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                        AppRoomDatabase.class,
                        "Account"
                )
                                        .build();
                }
            }
        }

        return INSTANCE;
    }
}

DAO Base Class

This class is not part of the Room Components Library, rather I have added this to keep source code clean by adding common source code into one class. 

You can also utilize this and add your common stuff here if you find anything in your DAO Class.

package com.example.database2;

import android.database.Cursor;
import java.util.List;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.RawQuery;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;

/**
 * Class holds the common functionality for all the DAO classes
 * s.a. insert, update, delete and sqlite functions
 */
public interface BaseCommonDao {

    /**
     * Sqlite functions
     */
    interface SqliteFunctions {

        String AVERAGE = "avg";
        String COUNT = "count";
        String MAX = "max";
        String MIN = "min";
        String SUM = "sum";
        String TOTAL = "total";
    }

    /**
     * Forced to be implemented by all its child classes
     * It will be utilized for Sqlite function execution
     *
     * @return the table name
     */
    String getTableName();

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    List insertAll(List data);

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    Long insert(T data);

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    Long update(T data);

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    List updateAll(List data);

    @Delete
    int delete(T obj);

    @RawQuery
    Cursor executeSqliteFunction(SupportSQLiteQuery query);

    /**
     * To perform the execution of Sqlite functions
     * s.a avg, count, max, min, sum, total, ..
     *
     * e.g function("max", "id", null);
     * will return the maximum value of 'id' column
     *
     * Returns the sqlite function response of the given [functionType] = BaseCommonDao.SqliteFunctions.MAX, BaseCommonDao.SqliteFunctions.MIN, etc..,
     * [columnName] or [condition], if any.
     */
    default Long function(String functionType, String columnName, String condition) {

        Long result = 0l;
        Cursor cursor;
        String rawQuery = (" select " + functionType + "(" + columnName
                + ")" + " from " + getTableName());
        if (condition != null && condition.length() > 0) {
            rawQuery += " where " + condition;
        }

        SupportSQLiteQuery simpleSQLiteQuery = new SimpleSQLiteQuery(rawQuery.toString());
        cursor = executeSqliteFunction(simpleSQLiteQuery);

        if (cursor.moveToFirst()) {
            result = cursor.getLong(0);
            cursor.close();
        }

        return result;
    }
}


Database Constant Class

This is added to keep all the table information on the same place. You may observe, that I have defined all the SQLite data structure into this file. 

Again, this is not mandatory but the only intention is to keep the source code clean and avoid duplicate entries.

package com.example.database2;

public class  DatabaseConstants {

	public interface BaseConstants {


			// If you change the database schema, you must increment the database version
		int DATABASE_VERSION = 1;
		String DATABASE_NAME = "database.db";
	}

	interface AccountTableKey {

			String TABLE_NAME = "account";

			String ACCOUNT_ID_FIELD = "account_id";
			String NAME_FIELD = "name";
			String AGE_FIELD = "age";
			String DATE_FIELD = "date";
			String EARNINGS_FIELD = "earnings";
			String MORE_FIELD = "more";
        }

	interface ArtistTableKey {

			String TABLE_NAME = "artist";

			String ARTIST_ID_FIELD = "artist_id";
			String ACCOUNT_FK_FIELD = "account_fk";
			String AGE_FIELD = "age";
        }

	interface InstrumentTableKey {

			String TABLE_NAME = "instrument";

			String ID_FIELD = "id";
			String TYPE_FIELD = "type";
			String ACT_FK_FIELD = "act_fk";
			String ARTIST_FK_FIELD = "artist_fk";
        }

}

Conclusion

Now, lets look at the alternative.

To write and maintain all this source code is going to be an effort intensive task. I am referring you to this tool which will help you generate all these source code files in few clicks.

Click here to know more about ‘code generator’ and how to use this in your live project to increase your productivity.

Please share your comments, I would love to hear!

Don’t miss to subscribe, I will be sharing more contents on software programming practices.

Full Source Code

To download the full sample code for Android Room using Java, click on the button given below:

0

3 thoughts on “Android Room using Java

Leave a Reply

Your email address will not be published. Required fields are marked *