Saturday, May 26, 2018

Android - Room persistance

Android OS provides us inbuilt SQLite core library for store raw data in local database. It handle CRUD (Create, Read, Update and Delete) operations that require for a database. SQLite maintains an effective database management system.

But, Google has introduced Room persistence library which allows fluent database access while harnessing the full power of SQLite. Room is a new way to create a database in your android apps, it is much similar OrmLite. Let's start with a most obvious question about Room.

Why should I use another library to manage my database when I am already well equipped with SQLite usage?

Let's review problem with SQLite usage.

1. There is no compile-time verification of raw SQL queries.

Example:- If you write a SQL query with a wrong column name that does not exist in real database then SQLite will give exception during run time and you can not capture this issue during compile time.
2. If you want change or update the database with the help of SQL queries. This process may be time consuming and produce error.
3. We have to use lots of boilerplate code to convert between SQL queries and Java data objects.

To overcome this, Google has introduced Room Persistence Library. It has an abstraction layer for the existing SQLite APIs. All the required packages, parameters, methods, and variables are imported into an Android project by using simple following annotations.
Room Persistence Annotations
@Entity: Creates a SQLite table in the database using a data model class. @Dao: Create a Data Access Object in the database using an interface class. @Database: A class with this annotation will create an abstraction for the Data Access Object. @PrimaryKey: A variable with this annotation will set a primary key for the table. @Insert: Inserts parameters into the table. @Update: Updates parameters of an existing table. @Delete: Deletes parameters of an existing table. @Query: Running SQL query method within the table. @Ignore: Ignores the parameter form the Room database.
Room have three major components that we need create and handle database quarries.

1. Database: By using database annotation you can create data holder class. Here, we have to declare entities and version of database. The annotated class should be an abstract class that extends RoomDatabase. At runtime, you can acquire an instance of it by calling Room.databaseBuilder() or Room.inMemoryDatabaseBuilder().

@Database(entities = {User.class}, version = 1) public abstract class AppDatabase extends RoomDatabase { public abstract UserDao userDao(); }

2. DAO:  DAOs are the main component of Room. It will handle all the queries and define methods that access the database. As you can see, each query define own action.
@Dao public interface UserDao {
@Query("SELECT * FROM user") List<User> getAll();
@Query("SELECT * FROM user WHERE uid IN (:userIds)") List<User> loadAllByIds(int[] userIds);
@Query("SELECT * FROM user WHERE first_name LIKE :first AND " + "last_name LIKE :last LIMIT 1") User findByName(String first, String last);
@Insert void insertAll(User... users);
@Delete void delete(User user); }

3. Entity : This component represents a database row of table. Each field of the entity is represent a column in the database.
@Entity class User {
@PrimaryKey public int id; public String firstName; public String lastName;

We have seen all the basic components and annotations of Room. Let's dive into implementation and understand it. We going to create a simple database that will store user information like: first name, last name, age.

1. Create a new project in Android Studio with empty activity and add following dependencies in module build.gradle.
Room Gradle  dependencies 
def room_version = "2.0.0-alpha1" implementation "$room_version" annotationProcessor "$room_version"

2. Now, lets create an entity called User. It defines a attributes of your table and here i'm going to declare one field as primary key. It has property to auto generate values. Class is annotated with @Entity and name of the table. To make field primary key, you need to annotate a field with @PrimaryKey and property autoGenerate which assign automatic IDs. Room will create a user table with defined attributes.
User Table (Entity) 
@Entity(tableName = "user") public class User { @PrimaryKey(autoGenerate = true) private int uid; @ColumnInfo(name = "first_name") private String firstName; @ColumnInfo(name = "last_name") private String lastName; @ColumnInfo(name = "age") private int age; public int getUid() { return uid; } public void setUid(int uid) { this.uid = uid; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }

3. Now create a data access object using an interface as define above. This class is annotated with @Dao annotation. Room will generate an implementation of defined methods. There are three annotations @Query, @Insert, @Delete to perform CRD operations. @Query annotation is used to perform read operation on database.
Dao interface 
@Dao public interface UserDao { @Query("SELECT * FROM user") List<User> getAll(); @Query("SELECT * FROM user where first_name LIKE :firstName AND last_name LIKE :lastName") User findByName(String firstName, String lastName); @Query("SELECT COUNT(*) from user") int countUsers(); @Insert void insertAll(User... users); @Delete void delete(User user); }

4. After that, create a database holder class that extends RoomDatabase. Here, i'm going to define user entity and database version. You can declare all the entities if you have. Class is annotated with @Database annotation. It is good practice to use singleton approach for the database, so you need to create an static method which will return instance of AppDatabase.
@Database(entities = {User.class}, version = 1) public abstract class AppDatabase extends RoomDatabase { private static AppDatabase INSTANCE; public abstract UserDao userDao(); public static AppDatabase getAppDatabase(Context context) { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "user-database") // allow queries on the main thread. // Don't do this on a real app! See PersistenceBasicSample for an example. .allowMainThreadQueries() .build(); } return INSTANCE; } public static void destroyInstance() { INSTANCE = null; }

private static User addUser(final AppDatabase db, User user) { db.userDao().insertAll(user); return user; } }

You are done!. We have setup database with a data holder user class that represent a row and an interface that we will use for perform CURD operations.

Now,  you can add users in the user table of the database. You must perform queries on worker thread, otherwise your application will crash.
Add a row.
//Always use a Thread, AsyncTask, or any worker threads to perform database operations.
public void populateWithTestData(AppDatabase db) {
new Thread(new Runnable() { @Override
public void run() { User user = new User(); user.setFirstName("sun"); user.setLastName("kum") user.setAge(25); AppDatabase.addUser(db, user);
} }) .start();

At the end, I found various thing about Room.

1. I found it easier than using SQLite directly.

2. It makes you verify your database code during compile time.
3. It makes your code more modular and readable as you divide your database code in three components like Entity , DAO and Database.

If still you have any doubt, please feel free to clarify that in the comment section below. Your Comments and suggestions are welcome.


Get it on Google Play

Android - Scoped storage OS 11

Every Android updated version has something for developers and users alike. The Android 10 version has many user-friendly and performance en...