Room Persistent Data Storage Library Tutorial

Hey Technoz, Hope you are having a good time playing with your code. So today in this tutorial, we are going to learn a smart, simple, and effective way of saving data offline in an android SQLite database with Room, a persistent data storage library. With the help of Room library, it becomes very easy to store, update, retrieve and delete the data from the SQLite database. So let’s jump into the implementation. As usual, create a new android studio project or continue with an existing one.

Add a Room library dependency

We are now adding the dependency for Room library. For that, just go to this link of official documentation and copy the latest two dependencies at the top from there. or copy the below dependencies in app-level build.gradle file. Just make sure you are using the latest version.

//Room library
def room_version = "2.2.6"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"

Now, we are all set to go further. In order to create a new SQLite database, we need to create a new POJO (Plain Old Java Object) class with the required fields. Room library then converts these fields to columns.

Creating a table class

Here, we are going to build a database for saving product details. create a new class Products.java and paste the following code in it.

package net.softglobe.roomtutorial;

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

@Entity
public class Products {
    @PrimaryKey(autoGenerate = true)
    int id;
    @ColumnInfo
    String name;
    @ColumnInfo
    String description;
    @ColumnInfo
    int price;

    public Products(String name, String description, int price) {
        this.name = name;
        this.description = description;
        this.price = price;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

In the above class, we are declaring required fields for product details and creating constructor, getters, and setters. The Room library treats the class as an entity, thus we are declaring the class as entity and are putting @Entity above the class name. For id field, we are setting it as @PrimaryKey with autoGenerate to true. Thus, it will be a primary key and values will be automatically generated for it. For other fields, we are declaring them as @ColumnInfo so that they will be converted as columns in the background. The column names will be same as the variable names. This class will act as a table template in SQLite database. Again, the table name will be same as a class name.

Create an interface for queries

Now, we are creating a new interface as DAO (Data Access Object) in which we will write our SQL queries and get back the results. This class connects with Room database in background, executes queries, and gets back the results. So create a new java interface as ProductsDao.java and write the following code in it.

package net.softglobe.roomtutorial;

import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.RawQuery;
import androidx.sqlite.db.SimpleSQLiteQuery;

@Dao
public interface ProductsDao {

    @Insert
    void insertProducts(Products... products);

    @Query("SELECT COUNT(*) FROM Products")
    int countItems();

    @Query("SELECT * FROM Products")
    Products[] getProducts();

    @Query("DELETE FROM Products")
    void deleteAll();
}

In the above interface, we have put an annotation @Dao above the declaration to let Room library know we are using this class as DAO. Also, we are inserting the single or multiple products using @Insert annotation and for other queries which needs output data, we are using @Query. We can write any valid SQL query inside and get back the results. The Room library takes care of generating the background code. This is the power of Room library.

Declare the database class

Now comes the creation of database. Room library does this task also using annotations. So lets go forward and create a class ProductsDatabase.java with the following code.

package net.softglobe.roomtutorial;

import android.content.Context;

import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

@Database(entities = {Products.class}, version = 1)
public abstract class ProductsDatabase extends RoomDatabase {
    private static ProductsDatabase instance;

    public abstract ProductsDao productsDao();

    public static ProductsDatabase getInstance(Context context){
        if (instance == null){
            instance = Room.databaseBuilder(context.getApplicationContext(), ProductsDatabase.class, "products_db").allowMainThreadQueries().build();
        }
        return instance;
    }

    public static void destroyInstance(){
        instance = null;
    }
}

We have annotated the above class as @Database in which we are declaring the table class. If there are multiple, then we can separate them by commas. But in our example, we have only one class. and the next parameter is the database version same as we declare in SQLite. We are extending this class with RoomDatabase. Further, we are making a static instance of this class to refer the public methods and declaring the ProductsDao as an abstract class so that through this class, we can call the methods there. Later we are creating the method for getting and setting an instance. We are using databaseBuilder method of Room database and declaring the database class (this class) and a string parameter in it, which is our database name. You can set it as per ur choice.

Please note that all database operation with Room library must happen in the background thread. But to force Room do that tasks in main thread, we are adding a method allowMainThreadQueries() and it will allow it. But this is not recommended. I will recommend to always do it in background when you go for building large scale production app. At last, we have written a method to destroy the instance we created to prevent data leaks.

The Driver Code for Room Library Tutorial

So far we were creating the backend functionality for the Room library. Now, we are going to build the MainActivity for accessing the features of Room library. We are adding and displaying the data using Room library.

UI First

Let’s build the UI first by creating activity_main.xml with the following code.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:orientation="vertical"
        android:layout_margin="10sp">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Room Library Tutorial"
            android:gravity="center_horizontal"
            android:textSize="25sp"
            android:textStyle="bold"/>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/black"
            android:height="1sp"
            android:layout_margin="5sp"/>
        <TextView
            android:layout_marginTop="5sp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Add new Product"
            android:textSize="20sp"
            android:gravity="center_horizontal"
            android:textStyle="bold|italic"/>
        <TableLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:stretchColumns="*"
            android:shrinkColumns="*">
            <TableRow>
                <TextView
                    android:text="Name:"
                    android:textSize="20sp"
                    android:textColor="@android:color/black"/>
                <EditText
                    android:id="@+id/name"
                    android:hint="Product name"/>
            </TableRow>
            <TableRow>
                <TextView
                    android:text="Description:"
                    android:textSize="20sp"
                    android:textColor="@android:color/black"/>
                <EditText
                    android:id="@+id/desc"
                    android:hint="Product Description"
                    android:inputType="text"/>
            </TableRow>
            <TableRow>
                <TextView
                    android:text="Price:"
                    android:textSize="20sp"
                    android:textColor="@android:color/black"/>
                <EditText
                    android:id="@+id/price"
                    android:hint="Product Price"
                    android:inputType="number"/>
            </TableRow>
        </TableLayout>
        <Button
            android:id="@+id/addBtn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Add Product"/>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/black"
            android:height="1sp"
            android:layout_margin="5sp" />
        <TextView
            android:layout_marginTop="5sp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="All Products"
            android:textSize="20sp"
            android:gravity="center_horizontal"
            android:textStyle="bold|italic"
            android:layout_marginBottom="5sp"/>

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TableLayout
                android:id="@+id/products_table"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:stretchColumns="*"
                android:shrinkColumns="*"
                android:paddingBottom="10sp"/>
        </ScrollView>
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

We are creating the 3 fields as Name, description and price to enter by user and on submitting these details, it will be saved to the database.

Then Backend

Now we will create the backend code in MainActivity.java file as follows.

package net.softglobe.roomtutorial;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.Toast;

import org.w3c.dom.Text;

public class MainActivity extends AppCompatActivity {

    private ProductsDatabase db;
    public EditText etName, etDescription, etPrice;
    public TableLayout tl;
    public Button addBtn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        db = ProductsDatabase.getInstance(this);

        etName = findViewById(R.id.name);
        etDescription = findViewById(R.id.desc);
        etPrice = findViewById(R.id.price);
        addBtn = findViewById(R.id.addBtn);
        addBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!TextUtils.isEmpty(etName.getText()) && !TextUtils.isEmpty(etDescription.getText()) && !TextUtils.isEmpty(etPrice.getText())) {
                    Products product = new Products(etName.getText().toString(), etDescription.getText().toString(), Integer.parseInt(etPrice.getText().toString()));
                    db.productsDao().insertProducts(product);
                    Toast.makeText(MainActivity.this, "Product added successfully!", Toast.LENGTH_SHORT).show();
                } else
                    Toast.makeText(MainActivity.this, "Please fill all the fields", Toast.LENGTH_SHORT).show();
                getProducts();
            }
        });
        getProducts();
    }

    public void getProducts(){
        tl = findViewById(R.id.products_table);
        tl.removeAllViews();
        Products[] productsArr = db.productsDao().getProducts();
        for (Products prod: productsArr) {
            TextView tv_name = new TextView(this);
            tv_name.setText("Name: ");
            tv_name.setTextSize(20);

            TextView tv_name_val = new TextView(this);
            tv_name_val.setText(prod.getName());
            tv_name_val.setTextSize(20);

            TableRow row1 = new TableRow(this);
            row1.addView(tv_name);
            row1.addView(tv_name_val);
            tl.addView(row1);

            TextView tv_desc = new TextView(this);
            tv_desc.setText("Description: ");
            tv_desc.setTextSize(20);

            TextView tv_desc_val = new TextView(this);
            tv_desc_val.setText(prod.getDescription());
            tv_desc_val.setTextSize(20);

            TableRow row2 = new TableRow(this);
            row2.addView(tv_desc);
            row2.addView(tv_desc_val);
            tl.addView(row2);


            TextView tv_price = new TextView(this);
            tv_price.setText("Price: ");
            tv_price.setTextSize(20);

            TextView tv_price_val = new TextView(this);
            tv_price_val.setText(Integer.toString(prod.getPrice()));
            tv_price_val.setTextSize(20);

            TableRow row3 = new TableRow(this);
            row3.addView(tv_price);
            row3.addView(tv_price_val);
            tl.addView(row3);

            TextView tvLine = new TextView(this);
            tvLine.setBackgroundColor(getResources().getColor(R.color.black));
            tvLine.setHeight(1);
            tvLine.setPadding(5,5,5,5);
            tl.addView(tvLine);
        }
    }
}

In the class, we are first declaring a variable for ProductsDatabase class and then in OnCreate() method, we are initializing it by creating an instance of database. We are using getInstance() method for this task, which we created earlier in ProductsDatabase class. In the click event of the Add button, we are parsing the inputs provided by user and also inserting the data. We have already implemented the interface ProductsDao and created its instance in ProductsDatabase class. Using that instance, we are calling the method insertProducts(), defined in the interface and passing the Product object as an argument. The Room library will automatically generate a background code for the method and insert a new row with the new values and then we are showing a Toast text as a confirmation.

Followed by it, we are calling a method getProducts() to get the data from database. In that method, we are using the same database instance variable and calling a method getProducts() which returns an array of product objects. Furthermore, we are displaying these products in a tableview. So, lets have a look at the results of our hard work in the following screens.

Room Persistent Data Storage Library Tutorial with SQLite database - Technopoints

Room Persistent Data Storage Library Tutorial - Technopoints

That’s it! We have successfully built the app with Room library tutorial with an SQLite database. Let’s give a try and let me know if any questions, put them in the comments box below.

Download Source Code

Thanks for reading this tutorial. If you want to skip coding and jump directly on the implementation, then you can simply clone the GitHub repository below.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.