Android Room with LiveData Example Tutorial

If you are new to android application development or you have been working with android, then you will be familiar with SQLite which is an inbuilt local database for android.

SQLite database has been used in many android application since inception. Although it works so well but it has it own disadvantages just like many other android tools out there.

To improve some of these drawbacks, companies and individuals have created third party database libraries as an alternative to android SQLite.

Some of the third party database libraries are listed below with associated tutorial I wrote on how to use them.

1. Sugar ORM – This is an object relational mapper that wrap SQLite databasse. It map sqlite table to a java plain object. I wrote a simple tutorial about it here.

2. Realm Database – It provides offline-first functionality & data persistence through an easy-to-use API. You can find my example tutorial here.

3. SQLBrite – A lightweight wrapper around SQLiteOpenHelper which introduces reactive stream semantics to SQL operations

Back to android SQLite database, I also wrote a tutorial on this topic which have helped lots of people to get their heads around sqlite in an easy and fun way. If you have not seen the tutorial you can follow it on this link – Android SQLite Database Tutorial.

Android recently introduce the Architecture Component which is a collection of libraries that help you design robust, testable, and maintainable apps. Start with classes for managing your UI component lifecycle and handling data persistence.

Before the introduction of Architecture Component, developers have struggled to manage android component lifecycles on there own. If not well handled, it can lead to application crash, memory leak or bugs that are hard to find.

The new android Architecture Component comes with a new lifecycle-aware components that help you manage your activity and fragment lifecycles. Survive configuration changes, avoid memory leaks and easily load data into your UI using LiveData, ViewModel, LifecycleObserver and LifecycleOwner

Also, it includes Room – a SQLite object mapping library that help you avoid boilerplate code and easily convert SQLite table data to Java objects. Room provides compile time checks of SQLite statements and can return RxJava, Flowable and LiveData observables.

In this tutorial, we are going to create a simple message like note taking app. Below is the screenshot of the application we will create.

Create new android project

Lets start to soil our hands in code. Start up your IDE. For this tutorial, I am using the following tools and environment, feel free to use what works for you.

Windows 10

Android Studio

Sony Xperia H1313

Min SDK 16

Target SDK 26

To create a new android application project, follow the steps as stipulated below.

Go to File menu

Click on New menu

Click on Android Application

Enter Project name: AndroidRoomTutorial

Package: com.inducesmile.androidroomtutorial

Select Empty Activity

Name your activity: IntroActivity

Keep other default selections

Continue to click on next button until Finish button is active, then click on Finish Button.

Add library dependencies in build.gradle

In order to use android room and livedata, we are going to add its libraries so that we can get access to different classes and methods in the api. We will also add the annotation library.

Also, we will make use of arrow or pure functions in the project so make sure that your are using Java 8 which supports lambda.

Below is how the app module build.gradle should look like

apply plugin: 'com.android.application'
android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.inducesmile.androidroomtutorial"
        minSdkVersion 16
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        targetCompatibility 1.8
        sourceCompatibility 1.8
    }
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation "android.arch.persistence.room:runtime:1.0.0"
    annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
    implementation "android.arch.lifecycle:livedata:1.1.1"
    annotationProcessor "android.arch.lifecycle:compiler:1.1.1"
    implementation 'com.android.support:design:26.1.0'
    implementation 'com.android.support:cardview-v7:26.1.0'
    implementation 'com.android.support:recyclerview-v7:26.1.0'
    implementation 'com.android.support:support-v4:26.1.0'
    implementation 'com.intuit.sdp:sdp-android:1.0.3'
    implementation 'joda-time:joda-time:2.9.9'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

String.xml

We are going to update our project strings.xml file located in the values folder inside the res folder. Open the file and add the code below to it.

<resources>
    <string name="app_name">Android Room</string>
    <string name="enter_message">Enter message</string>
</resources>

Color.xml

Open the colors.xml file in the same location as the strings.xml file and add the code below to the file.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#01796F</color>
    <color name="colorPrimaryDark">#015f57</color>
    <color name="colorAccent">#fd5f6d</color>
    <color name="colorWhite">#ffffff</color>
    <color name="colorBlack">#000000</color>
    <color name="colorText">#cccccc</color>
</resources>

Create IntroActivity.java

The IntroActivity class is the first activity your will see when the application is launch. It contain an image button when clicked will navigate you to the main application page. The code snippet for the class is shown below.

IntroActivity.java

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
public class IntroActivity extends AppCompatActivity {
    private final String TAG = IntroActivity.class.getSimpleName();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intro);
        ActionBar actionBar = getSupportActionBar();
        if(actionBar != null){
            actionBar.hide();
        }
        Button roomBtn = (Button)findViewById(R.id.enter_room);
        roomBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent roomIntent = new Intent(IntroActivity.this, RoomActivity.class);
                startActivity(roomIntent);
            }
        });
    }
}

The code sample for activity_intro.xml

activity_intro.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    android:background="@color/colorPrimary"
    tools:context="com.inducesmile.androidroomtutorial.IntroActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="@drawable/room"
        android:layout_weight="1">
    </LinearLayout>
    
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/colorWhite"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="20dp"
            android:text="Android Room Example"/>
        <Button
            android:id="@+id/enter-room"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:background="@color/colorAccent"
            android:textColor="@color/colorWhite"
            android:text="Enter room"
            android:paddingRight="20dp"
            android:paddingLeft="20dp"
            android:textSize="12sp"
            android:layout_alignParentRight="true"
            android:layout_alignParentBottom="true"/>
    </RelativeLayout>
    
</LinearLayout>

Create a new package and name it adapter. The adapter package will contain the message adapter class and the message viewholder class.

RoomAdapter.java

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.inducesmile.androidroomtutorial.R;
import com.inducesmile.androidroomtutorial.database.Message;
import org.joda.time.DateTime;
import java.util.Date;
import java.util.List;
public class RoomAdapter extends RecyclerView.Adapter<RoomViewHolder> {
    private List<Message> messageList;
    private Context context;
    public RoomAdapter(Context context, List<Message> messageList) {
        this.messageList = messageList;
        this.context = context;
    }
    @Override
    public RoomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_layout, parent, false);
        return new RoomViewHolder(view);
    }
    @Override
    public void onBindViewHolder(RoomViewHolder holder, int position) {
        Message message = messageList.get(position);
        String currentDate = getCurrentDateTime();
        holder.content.setText(message.getContent());
        holder.contentDate.setText(currentDate);
        holder.contentDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // delete a message with the following id
            }
        });
    }
    @Override
    public int getItemCount() {
        return messageList.size();
    }
    private String getCurrentDateTime(){
        Date currentDate = new Date();
        DateTime dt = new DateTime(currentDate);
        return dt.toString();
    }
}

Open the RoomViewHolder class and the code below.

RoomViewHolder.java

import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.inducesmile.androidroomtutorial.R;
public class RoomViewHolder extends RecyclerView.ViewHolder{
    public TextView content;
    public ImageView contentDelete;
    public RoomViewHolder(View itemView) {
        super(itemView);
        content = (TextView)itemView.findViewById(R.id.content);
        contentDate = (TextView)itemView.findViewById(R.id.content_date);
        contentDelete = (ImageView)itemView.findViewById(R.id.content_delete);
    }
}

Add a Dialog with EditText Field

To add a new message to Android room we need to create a dialog box that contains an EditText view. Create a new package and name it utils. Inside the utils folder, create a new java file and name it MessageDialog.java.

Open the MessageDialog.java class and add the code below to it.

MessageDialog.java


import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import com.inducesmile.androidroomtutorial.R;
import com.inducesmile.androidroomtutorial.database.AppDatabase;
import com.inducesmile.androidroomtutorial.database.Message;
import com.inducesmile.androidroomtutorial.database.MessageDao;
public class MessageDialog {
    private Context context;
    public MessageDialog(Context context) {
        this.context = context;
    }
    public void addNewMessage(int dialog_layout){
        LayoutInflater inflater = LayoutInflater.from(context);
        View subView = inflater.inflate(dialog_layout, null);
        final EditText nameField = (EditText)subView.findViewById(R.id.enter_message);
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("Add new message");
        builder.setView(subView);
        builder.create();
        builder.setPositiveButton("ADD MESSAGE", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                final String message = nameField.getText().toString();
                if(TextUtils.isEmpty(message)){
                    Toast.makeText(context, "Empty or invalid input", Toast.LENGTH_LONG).show();
                }
                else{
                    Message content = new Message(message);
                    //add new message to database
                    MessageDao messageDao = (MessageDao) AppDatabase.getInstance(context).message();
                    messageDao.insertNewMessage(content);
                }
            }
        });
        builder.setNegativeButton("CANCEL", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(context, "Task cancelled", Toast.LENGTH_LONG).show();
            }
        });
        builder.show();
    }
}

Create a new package in your android project and name it database. Inside the database package, we are going to create the following java classes.

1. Message.java – this is Java object class that will be map to a message table in sqlite by android room. The class is annotated with @Entity. The attributes of the class will represent the columns of the database table. The id of the class can use the @PrimaryKey to specify that it is the table’s primary key. The autogenerate attribute can be set to true if we want the primary key to auto increment for us. The column name of the table can be mapped with @ColumnInfo annotation.

The sample code for this class is as shown below

Message.java


import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;
@Entity(tableName = "Message")
public class Message {
    @PrimaryKey(autoGenerate = true)
    private int id;
    @ColumnInfo(name = "content")
    private String content;
    public Message(String content) {
        this.content = content;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
}

2. MessageDao – this is an interface class and database access object use to query the message database. It contains all the queries that will be executed in the message database table.

The class is annotated with @Dao and the following annotation can be used for each method depending on the kind of CRUD function you want to execute – @Query, @Insert, @Update, @Delete.

@Query – it is use to retrieve data from the database table. Please note that you cannot call any method with @Query annotation in UI Thread. If you do so your application will crash. This can be achieve by using as one of this.

1. Background Thread like AsynTask
ii. RxJava Flowable
iii. LiveData.

In this tutorial, we will use LiveData since it runs asynchronously outside the UI Thread. Below is the code for this class.

MessageDao.java


import android.arch.lifecycle.LiveData;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;
import java.util.List;
import static android.arch.persistence.room.OnConflictStrategy.REPLACE;


@Dao
public interface MessageDao {

    @Query("Select * from Message")
    public LiveData<List<Message>> getAllMessage();

    @Delete
    public void deleteMessage(Message message);

    @Insert(onConflict = REPLACE)
    public void insertNewMessage(Message message);
}

3. AppDatabase.java – The abstract database class that extends the RoomDatabase. The MessageDao interface class is added as a public abstract member variable. The AppDatabase class is annotated with @Database. The entities attribute is assigned an object that contains all the Java object table mappers and the version is the current version of the database.

There are different things you can experiment when using AppDatabase class. You can use Dagger 2 as dependency injection which will let you add this class anywhere whenever it is need. In our case, created a singleton instance as shown in code below.

AppDatabase.java


import android.arch.persistence.room.Database;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.RoomDatabase;
import android.content.Context;

@Database(entities = {Message.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase{
    private static AppDatabase appDatabase;
    public abstract MessageDao message();
    private Context context;
    public static AppDatabase getInstance(Context context){
        if(appDatabase == null){
            appDatabase = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "Message-database")
                    .allowMainThreadQueries()
                    .build();
        }
        return appDatabase;
    }

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

So where is the LiveData we said you are going to use?

You are right. But if you look once more at MessageDao.java class, you will see the follow code,

@Query("Select * from Message")
public LiveData<List<Message>> getAllMessage();

What it means is that we are returning our query result as a LiveData object. If you want to learn more about LiveData and ViewModel, you can read my tutorial on this topic here.

Create a new Activity where we will display all the messages we stored in the room.

Create a new activity and name it RoomActivity.java.
Open the activity_room.xml layout file. We are going to add a RecyclerView and a Button View. When the Button is clicked, it will open the message dialog we created above. The RecyclerView will use the RoomAdapter.java and RoomViewHolder.java we created before to display our message contents.

Open the layout file activity_room.xml and add the code below.

 activity_room.xml


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="com.inducesmile.androidroomtutorial.RoomActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/add-btn"
        android:layout_alignParentTop="true"
        android:padding="20dp">
        <android.support.v7.widget.RecyclerView
            android:id="@+id/messages"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </android.support.v7.widget.RecyclerView>
    </LinearLayout>

    <ImageView
        android:id="@+id/add-btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:src="@drawable/plus"
        android:layout_margin="20dp"
        android:contentDescription="@string/app_name"/>

</RelativeLayout>

In RoomActivity.java, we are going to query our message table to get all the messages we stored in it and it will be passed to the RecyclerView adapter.

The complete code for the class is as shown below.

RoomActivity.java


import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;
import com.inducesmile.androidroomtutorial.adapter.RoomAdapter;
import com.inducesmile.androidroomtutorial.database.AppDatabase;
import com.inducesmile.androidroomtutorial.database.Message;
import com.inducesmile.androidroomtutorial.database.MessageDao;
import com.inducesmile.androidroomtutorial.utils.MessageDialog;
import java.util.List;

public class RoomActivity extends AppCompatActivity {

    private final String TAG = RoomActivity.class.getSimpleName();

    private RoomAdapter roomAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_room);
        RecyclerView recyclerView = (RecyclerView)findViewById(R.id.messages);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(RoomActivity.this);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.setHasFixedSize(true);
        MessageDao messageDao = (MessageDao) AppDatabase.getInstance(getApplicationContext()).message();
        messageDao.getAllMessage().observe(this, (List<Message> message) -> {
            roomAdapter = new RoomAdapter(RoomActivity.this, message);
            recyclerView.setAdapter(roomAdapter);
        });

        ImageView addMessageBtn = (ImageView) findViewById(R.id.add_btn);
        addMessageBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MessageDialog dialog = new MessageDialog(RoomActivity.this);
                dialog.addNewMessage(R.layout.dialog_layout);
            }
        });
    }
}

Assignment

This is not the usually tutorial but hands on tutorial – which means that after going through the tutorial, there is a task you will need to solve to make sure you understand the topic.
In the RoomAdapter.java class, I have taken out the code that delete each item of the message and I will like you to figure out how it can be implemented.

holder.contentDelete.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // add a code that can delete a message 
        // item when the delete icon is clicked
    }
});

Try it and share your code in the comment section or you can use pastebin and share the link with us.

Finally, you can get the complete source code for this tutorial on my Github account.

If you have questions or suggestions on a topic you will like me to post, kindly use the comment box.

Add a Comment