Android Room Database With Java[Beginners]

Shradha Rathod
10 min readJun 24, 2021

--

Room Database is a part of the Android architecture component, which allows us to structure our app in a way that is robust, testable, and maintainable with less boilerplate code. The Architecture Component libraries are part of Android Jetpack.

The basic form of Android Architecture

Steps in android architecture

  1. Create app
  • Open Android Studio and click Start a new Android Studio project.
  • In the Create New Project window, choose Empty Activity and click Next.
  • On the next screen, name the app RoomExampleWithJava, and click Finish.

2. Update Gradle file

  • In android studio click on the project tab and expand the Build script
  • Open build.gradle(Module: app)
  • Now add compile option inside the android block
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}

3.Replace dependencies block with

dependencies {
implementation "androidx.appcompat:appcompat:$rootProject.appCompatVersion"

implementation "androidx.room:room-runtime:$rootProject.roomVersion"
annotationProcessor "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"

implementation "androidx.lifecycle:lifecycle-viewmodel:$rootProject.lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata:$rootProject.lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-common-java8:$rootProject.lifecycleVersion"

implementation "androidx.constraintlayout:constraintlayout:$rootProject.constraintLayoutVersion"
implementation "com.google.android.material:material:$rootProject.materialVersion"
}

4. Now in build.gradle (Project: RoomExampleWithJava) file add version number at the end as given in the code below.

ext {
appCompatVersion = '1.2.0'
constraintLayoutVersion = '2.0.2'
coreTestingVersion = '2.1.0'
lifecycleVersion = '2.2.0'
materialVersion = '1.2.1'
roomVersion = '2.2.5'
}

5. Now sync project.

What is Room Database?

Why we use Room Database?

  • Room is a database layer on top of an SQLite database.
  • The room supports Compile-time verification of SQL queries. each @Query and @Entity is checked at the compile time.
  • Using Annotations to reduce the boilerplate code.
  • Easily integrated with other Architecture components like LiveData, RxJava.

Room vs SQLite

  • In the case of SQLite, There is no compile-time verification of raw SQLite queries. But in Room, there is SQL validation at compile time.
  • We need to use lots of boilerplate code to convert between SQL queries and Java data objects. But, Room maps our database objects to Java Object without boilerplate code.
  • Room is built to work with LiveData and RxJava for data observation, while SQLite does not.
  • As our schema changes, we need to update the affected SQL queries manually. Room solves this problem.

In-Room,

  • We define each entity as an object and the interaction as an interface and then we use annotations to add metadata both.
  • And room uses these annotated classes to create tables in the database and we use annotations to specify things such as which property represents the primary key.
  • So to work with the Room database, we specifically need some annotated classes. We use Object classes to define our table as@Entity. And interface to perform an action on this database as @Dao(data access object).

Let’s start creating a room database,

1. Create Entity class:

  • An entity represents an object or concept and its properties that we want to store in the database.
  • As such an entity class defines a table, and each instance of that class represents a row in the table.
  • And each property defines a column.

A Student is a common entity in our app and it has name,lastname,address and marks as its properties so they would be column for our database table.go along with,

                  tableName = "student_table"

Now I’m going to explain some annotations we are going to use for this entity.

We are annotating a whole class with an entity. By default name of the table for storing instances of this entity same as the class name or we can specify the table name differently from the name of the entity class.

@Entity (tableName = “student_table”)

So while creating room our table must have at least one primary key.

In our app our first property name for a unique primary key.

here is the code,

@Entity (tableName = "student_table")
public class Student {
@PrimaryKey
@NonNull
String name;
String lastName;
String address;
String marks;

public Student(String name, String lastName, String address, String marks) {
this.name = name;
this.lastName = lastName;
this.address = address;
this.marks = marks;
}

public String getName() {
return name;
}

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

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

public String getMarks() {
return marks;
}

public void setMarks(String marks) {
this.marks = marks;
}
}

2. Create DAO Interface:

  • DAO stands for Data access object.
  • StudentDao is an interface; DAOs must either be interfaces or abstract classes with @Dao annotation.
  • So that they interact with the database which tells the compiler interface's role is to define how to access data from a database.
  • A mapping of SQL queries to functions. When we use a DAO, we call the methods, and Room takes care of the rest.
  • For common database operation(CRUD- create, read, update, delete) the room provides some convenience annotations as follows,

Let’s walk through it:

  • StudentDao is an interface; DAOs must either be interfaces or abstract classes.
  • The @Dao annotation identifies it as a DAO class for Room.
  • void insert(Student student); Declares a method to insert one word:
  • The @Insert annotation is a special DAO method annotation where we don't have to provide any SQL! (There are also @Delete and @Update annotations for deleting and updating rows, but we are not using them in this app.)
  • onConflict = OnConflictStrategy.IGNORE: The selected on conflict strategy ignores a new student if it's exactly the same as one already on the list. To know more about the available conflict strategies, check out the documentation.
  • deleteAll(): declares a method to delete all the words.
  • There is no convenience annotation for deleting multiple entities, so it’s annotated with the generic @Query.
  • @Query("DELETE FROM student_table"): @Query requires that you provide a SQL query as a string parameter to the annotation.
  • List getStudent(): A method to get all the words and have it return a List of student.
  • @Query("SELECT * FROM student_table ORDER BY name ASC"): Returns a list of students sorted in ascending order.
  • In StudentDao, change the getStudent() method signature so that the returned List is wrapped with LiveData:

here is a code here,

@Dao
public interface StudentDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
void insert(Student student);

@Update
void update(Student student);

@Query("SELECT * from student_table ORDER By name Asc")
LiveData<List<Student>> getStudent();

@Query("DELETE from student_table")
void deleteAll();
}

Later in this example, you track data changes via an Observer in MainActivity.

See the LiveData documentation to learn more about other ways of using LiveData.

3.Create Room database class :

  • Our Room database class must be abstract and extend RoomDatabase. Usually, we only need one instance of a Room database for the whole app.
  • This is where we define the entities (tables)and the version number of our database @Database.
  • The database exposes DAOs through an abstract “getter” method for each @Dao.
  • We’ve defined a singleton, StudentRoomDatabase, to prevent having multiple instances of the database opened at the same time.
  • getDatabase returns the singleton. It'll create the database the first time it's accessed, using Room's database builder to create an RoomDatabase object in the application context from the StudentRoomDatabase class and names it "student_database".
  • We’ve created an ExecutorService with a fixed thread pool that you will use to run database operations asynchronously on a background thread.
@Database(entities = {Student.class}, version = 1, exportSchema = false)
public abstract class StudentRoomDatabase extends RoomDatabase {
public abstract StudentDao studentDao();

private static volatile StudentRoomDatabase studentRoomDatabase;
private static final int NUMBER_OF_THREADS = 4;
static final ExecutorService databaseWriteExecutor =
Executors.newFixedThreadPool(NUMBER_OF_THREADS);

static StudentRoomDatabase getDatabase(final Context context) {
if (studentRoomDatabase == null) {
synchronized (StudentRoomDatabase.class) {
if (studentRoomDatabase == null) {
studentRoomDatabase = Room.databaseBuilder(context.getApplicationContext(),
StudentRoomDatabase.class, "student_database")
.build();
}
}
}
return studentRoomDatabase;
}
}

3.Create the Repository :

What is a Repository?

-The Repository is not part of the Architecture Components libraries but is a suggested best practice for code separation and architecture

-A Repository manages queries and allows us to use multiple backends.

In the most common example, the Repository implements the logic for deciding whether to fetch data from a network or use results cached in a local database.

Implementing the Repository:

As we stated above Repository class evermore consider for logic.

— So first of all declare Studentdatabase and StudentDao here.

Then we have to get data of StudentDao indirectly from StudentRoomdatabase by creating studentRoomdatabase object inside the StudentRepository constructor.

The getAllStudents method returns the LiveData list of words from Room; we can do this because of how we defined the getStudent method to return LiveData in the "The LiveData class" step. Room executes all queries on a separate thread. Then observed LiveData will notify the observer on the main thread when the data has changed.

public class StudentRepository {
StudentRoomDatabase studentRoomDatabase;
StudentDao studentDao;
private LiveData<List<Student>> listStudents;

public StudentRepository(Application application) {
studentRoomDatabase = StudentRoomDatabase.getDatabase(application);
studentDao = studentRoomDatabase.studentDao();
listStudents = studentDao.getStudent();
}

public void insertStudent(Student student) {
StudentRoomDatabase.databaseWriteExecutor.execute(() -> studentDao.insert(student));
}

public LiveData<List<Student>> getAllStudents() {
return listStudents;
}
}

4.Create Viewmodel:

What is a ViewModel?

The ViewModel's role is to provide data to the UI and survive configuration changes. A ViewModel acts as a communication center between the Repository and the UI. You can also use a ViewModel to share data between fragments. The ViewModel is part of the lifecycle library.

Why use a ViewModel?

  • A ViewModel holds our app's UI data in a lifecycle-conscious way that survives configuration changes.
  • Separating our app's UI data from our Activity and Fragment classes let us better follow the single responsibility principle: Our activities and fragments are responsible for drawing data to the screen, while our ViewModel can take care of holding and processing all the data needed for the UI.

In the ViewModel, We use LiveData for changeable data that the UI will use or display. Using LiveData has several benefits:

What LiveData will do for us?

  • With the help of LiveData we can put an observer on the data and only update the UI when the data actually changes.
  • The Repository and the UI are completely separated by the ViewModel.
  • There are no database calls from the ViewModel (this is all handled in the Repository), making the code more testable.

Implementing the ViewModel

Create a class file for StudentViewModel and add this code to it:

public class StudentViewModel extends AndroidViewModel {
private StudentRepository studentRepository;
private final LiveData<List<Student>> listLiveData;

public StudentViewModel(Application application) {
super(application);
studentRepository = new StudentRepository(application);
listLiveData = studentRepository.getAllStudents();
}

public LiveData<List<Student>> getAllStudentsFromVm() {
return listLiveData;
}

public void insertStudent(Student student) {
studentRepository.insertStudent(student);
}
}
  • here we created a class StudentViewModel that get the application as a parameter and extends AndroidViewModel.
  • We added a private member variable to hold a reference to the repository.
  • We created StudentRepository in the constructor.
  • In the constructor, initialize the listLiveData LiveData using the repository.
  • Created a wrapper insertStudent() method that calls the Repository's insertStudent() method. In this way, the implementation of insertStudent() is encapsulated from the UI.

ViewModelFactory Implemented

This is because implementations of Factory interfaces are responsible to instantiate ViewModels.

public class StudentViewModelFactory implements ViewModelProvider.Factory {
private final Application application;
public StudentViewModelFactory(Application myApplication) {
application = myApplication;
}

@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new StudentViewModel(application);
}
}

Now we are going to display the data in a RecyclerView, Which is a little more precise than just throwing the data in a TextView.

4.Create RecyclerView:

public class StudentListAdapter extends RecyclerView.Adapter<StudentListAdapter.StudentViewHolder> {
ArrayList<Student> studentArrayList;

public StudentListAdapter(ArrayList<Student> students) {
this.studentArrayList = students;
}

@NonNull
@Override
public StudentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.recyclerview_item_list, parent, false);
return new StudentViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull StudentViewHolder holder, int position) {
holder.tvStudentName.setText(studentArrayList.get(position).name);
holder.tvStudentLastName.setText(String.format("%s", studentArrayList.get(position).lastName));
holder.tvStudentAddress.setText(studentArrayList.get(position).address);
holder.tvStudentMarks.setText(String.format("%s", studentArrayList.get(position).marks));
}

@Override
public int getItemCount() {
return studentArrayList.size();
}

static class StudentViewHolder extends RecyclerView.ViewHolder {
TextView tvStudentName;
TextView tvStudentLastName;
TextView tvStudentAddress;
TextView tvStudentMarks;

public StudentViewHolder(@NonNull View itemView) {
super(itemView);
findViews();
}

private void findViews() {
tvStudentName = itemView.findViewById(R.id.tvStudentName);
tvStudentLastName = itemView.findViewById(R.id.tvStudentLastName);
tvStudentAddress = itemView.findViewById(R.id.tvStudentAddress);
tvStudentMarks = itemView.findViewById(R.id.tvStudentMarks);
}
}

5.Connect with the data:

The final step is to connect the UI to the database by saving new Students details the user enters and displaying the current contents of the Student database in the RecyclerView.

To display the current contents of the database, we have to add an observer that observes the LiveData in the StudentViewModel.

Whenever the data changes, the onChanged() a callback is invoked, which calls the adapter's setWords() method to update the adapter's cached data and refresh the displayed list.

Now in MainActivity, Create a member variable for the StudentViewmodel:

We use ViewModelProvider to associate our StudentViewmodel with our Activity.

When our Activity first starts, the ViewModelProviders will create the ViewModel. When the activity is destroyed, for example through a configuration change, the ViewModel persists. When the activity is re-created, then ViewModelProviders return the existing ViewModel. For more information, see ViewModel.

So, here is the code for inserting and retrieving data from the database

public class MainActivity extends AppCompatActivity {
private EditText editStudentName;
private EditText editStudentLastName;
private EditText editStudentAddress;
private EditText editStudentMarks;
private Button buttonSave;
private RecyclerView recyclerView;
private StudentViewModel studentViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViews();
buttonSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Student student = new Student(editStudentName.getText().toString(),
editStudentLastName.getText().toString(),
editStudentAddress.getText().toString(),
editStudentMarks.getText().toString());
studentViewModel.insertStudent(student);
}
});
studentViewModel = ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication()).create(StudentViewModel.class);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
studentViewModel.getAllStudentsFromVm().observe(this, students ->
{
if (students != null && !students.isEmpty()) {
StudentListAdapter adapter = new StudentListAdapter((ArrayList<Student>) students);
recyclerView.setAdapter(adapter);
}
});
}

private void findViews() {
editStudentName = findViewById(R.id.editStudentName);
editStudentLastName = findViewById(R.id.editStudentLastName);
editStudentAddress = findViewById(R.id.editStudentAddress);
editStudentMarks = findViewById(R.id.editStudentMarks);
buttonSave = findViewById(R.id.buttonSave);
recyclerView = findViewById(R.id.recyclerView);
}

You can find the full code of activity_main.xml from here and MainActivity.java from here.

To access the finished project on GitHub

https://github.com/shrayash12/RoomExampleWithJava

--

--

Responses (1)