Data persistence is one of the basic requirements of most applications. SQLite, an open-source library is a means of persisting data for Android applications. The implementation of SQLite requires lots of boilerplate code, however. This has drawbacks:
These issues are quite common in Q&A forums, which is likely why popular No-SQL databases like Realm, GreenDAO and Room came along. Room is a persistent library that abstract away the most of the SQLite code using annotations.
This tutorial aims to cover:
Internal and External storage : Applications can store text or CSV files, images, videos in phone memory or inside public directories(kitkat or above) under SD card storage.
SQLite : SQLite is a light-weight relational database, embedded into the Android OS. The database schema is mapped to tables and integrity constraints.
NoSQL
simply means Objects or Documents. Instead of storing the data in tabular form, The data is stored in POJO form, which is extremely suitable for semi-structured or un-structured data when there is no fixed schema.Features | SQL | No-SQL |
---|---|---|
Data Stored | In Tabular form (RDBMS) | POJO objects or Documents |
Data Manipulation | via DML,DDL | via provided API's |
Structure | RDBMS | key-value pairs |
Schema | Fixed schema | dynamic, records can be added on the fly |
Scalable | RDBMS | key-value pairs |
Android Support | SQLite | Room(semi-sql), GreenDAO, Realm |
The Room library acts as an abstract layer for underlying SQLite database. Thus, Room annotations are used:
@Entity
annotation is mapped to a table in database. Every entity is persisted in its own table and every field in class represents the column name.tableName
attribute is used to define the name of the table@ColumnInfo(name = “name_of_column”)
annotation to give specific column namesDAO : Data Access Object is either be an interface or an abstract class annotated with @Doa
annotation, containing all the methods to define the operations to be performed on data. The methods can be annotated with
@Database
annotation is used to create a database with given name along with database version.version = intValueForDBVersion
is used to define the database versionentities = {EntityClassOne.class, ....}
is used to define list of entities for databaseHopefully, you are tempted enough to try Room library now! We will build a Notes App that will allow the user to:
The demo application we create here will hopefully demonstrate database-oriented application skills and get you started with Room. Conceptually, however, this code can further be extended or changed to build alarm apps, scheduling apps, SMS-driven applications, and more.
All the code is available at Github Repo
Create a new project in Android Studio. Choose "Basic Activity" template.
Next, add a Maven Repository. Open build.gradle
project and add following Maven dependency
1allprojects {
2 repositories {
3 google()
4 jcenter()
5 maven {
6 url "https://maven.google.com"
7 }
8 }
9}
Google Maven Repository provides all the updated support libraries, which will be downloaded by gradle instead of downloading from SDK Manager.
Then we will add a Room dependency in build.gradle
(Module:app) within the dependency block.
1dependencies {
2//... other dependencies
3
4 // Room dependencies
5 compile 'android.arch.persistence.room:runtime:1.0.0'
6 annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'
7
8}
Before creating a database, Let's create an Entity, named as Note
and later, Objects of this class will be added to database.
To do this:
class
named Note
.@Entity
annotation on the class.@PrimaryKey
annotation.alt+insert
to implement constructor, override
getter and setter, and optionally override equals
or toString
.1package com.example.pavneet_singh.roomdemo.notedb.model;
2
3import android.arch.persistence.room.Entity;
4import android.arch.persistence.room.PrimaryKey;
5
6@Entity
7public class Note {
8
9 @PrimaryKey(autoGenerate = true)
10 private int note_id;
11
12 @ColumnInfo(name = "note_content") // column name will be "note_content" instead of "content" in table
13 private String content;
14
15 private String title;
16
17 private
18
19 public Note(int note_id, String content, String title) {
20 this.note_id = note_id;
21 this.content = content;
22 this.title = title;
23 }
24
25 public int getNote_id() {
26 return note_id;
27 }
28
29 public void setNote_id(int note_id) {
30 this.note_id = note_id;
31 }
32
33 public String getContent() {
34 return content;
35 }
36
37 public void setContent(String content) {
38 this.content = content;
39 }
40
41 public String getTitle() {
42 return title;
43 }
44
45 public void setTitle(String title) {
46 this.title = title;
47 }
48
49 @Override
50 public boolean equals(Object o) {
51 if (this == o) return true;
52 if (!(o instanceof Note)) return false;
53
54 Note note = (Note) o;
55
56 if (note_id != note.note_id) return false;
57 return title != null ? title.equals(note.title) : note.title == null;
58 }
59
60
61
62 @Override
63 public int hashCode() {
64 int result = note_id;
65 result = 31 * result + (title != null ? title.hashCode() : 0);
66 return result;
67 }
68
69 @Override
70 public String toString() {
71 return "Note{" +
72 "note_id=" + note_id +
73 ", content='" + content + '\'' +
74 ", title='" + title + '\'' +
75 '}';
76 }
77}
DAOs define all methods to access database, annotated with @Dao
annotation. The DAO acts as a contract to perform CRUD operations on data within a database.
The following code will:
@Dao
annotation.1package com.example.pavneet_singh.roomdemo.notedb.dao;
2
3import android.arch.persistence.room.Dao;
4import android.arch.persistence.room.Delete;
5import android.arch.persistence.room.Insert;
6import android.arch.persistence.room.Query;
7import android.arch.persistence.room.Update;
8
9import com.example.pavneet_singh.roomdemo.notedb.model.Note;
10import com.example.pavneet_singh.roomdemo.util.Constants;
11
12import java.util.List;
13
14@Dao
15public interface NoteDao {
16 @Query("SELECT * FROM user "+ Constants.TABLE_NAME_NOTE)
17 List<Note> getAll();
18
19
20 /*
21 * Insert the object in database
22 * @param note, object to be inserted
23 */
24 @Insert
25 void insert(Note note);
26
27 /*
28 * update the object in database
29 * @param note, object to be updated
30 */
31 @Update
32 void update(Note repos);
33
34 /*
35 * delete the object from database
36 * @param note, object to be deleted
37 */
38 @Delete
39 void delete(Note note);
40
41 /*
42 * delete list of objects from database
43 * @param note, array of objects to be deleted
44 */
45 @Delete
46 void delete(Note... note); // Note... is varargs, here note is an array
47
48}
Now, we have table defined as Entity
and CRUD methods defined via NoteDao
. The last piece of the database puzzle is the database itself.
We will have to:
NoteDatabse
which extends RoomDatabase
.@Database(entities = {Note.class}, version = 1)
.Some things to remember:
.db
extension1package com.example.pavneet_singh.roomdemo.notedb;
2
3import android.arch.persistence.room.Database;
4import android.arch.persistence.room.Room;
5import android.arch.persistence.room.RoomDatabase;
6import android.content.Context;
7
8import com.example.pavneet_singh.roomdemo.notedb.dao.NoteDao;
9import com.example.pavneet_singh.roomdemo.util.Constants;
10
11@Database(entities = { Note.class }, version = 1)
12public abstract class NoteDatabase extends RoomDatabase {
13
14public abstract NoteDao getNoteDao();
15
16private static NoteDatabase noteDB;
17
18public static NoteDatabase getInstance(Context context) {
19if (null == noteDB) {
20noteDB = buildDatabaseInstance(context);
21}
22return noteDB;
23}
24
25private static NoteDatabase buildDatabaseInstance(Context context) {
26return Room.databaseBuilder(context,
27NoteDatabase.class,
28Constants.DB_NAME)
29.allowMainThreadQueries().build();
30}
31
32public void cleanUp(){
33noteDB = null;
34}
35
36}
Additionally, it is good to note that Room does not allow code execution on Main thread. Instead, allowMainThreadQueries
is used to allow the execution. However, using this is not recommended on real apps. This is just for demonstration instead use AsyncTask (or handler, rxjava). The AddNoteActivity.java
snippet demonstrates how to use AsyncTask.
The below snippet will demonstrate the working of insert, update, and delete functionality using the Room database.
In AddNoteActivity.java
EditText
and create Note
object.Note
object into database.1package com.example.pavneet_singh.roomdemo;
2
3import android.content.Intent;
4import android.os.AsyncTask;
5import android.os.Bundle;
6import android.support.design.widget.TextInputEditText;
7import android.support.v7.app.AppCompatActivity;
8import android.view.View;
9import android.widget.Button;
10
11import com.example.pavneet_singh.roomdemo.notedb.NoteDatabase;
12import com.example.pavneet_singh.roomdemo.notedb.model.Note;
13
14import java.lang.ref.WeakReference;
15
16public class AddNoteActivity extends AppCompatActivity {
17
18private TextInputEditText et_title,et_content;
19private NoteDatabase noteDatabase;
20private Note note;
21
22@Override
23protected void onCreate(Bundle savedInstanceState) {
24super.onCreate(savedInstanceState);
25setContentView(R.layout.activity_add_note);
26et_title = findViewById(R.id.et_title);
27et_content = findViewById(R.id.et_content);
28noteDatabase = NoteDatabase.getInstance(AddNoteActivity.this);
29Button button = findViewById(R.id.but_save);
30
31 button.setOnClickListener(new View.OnClickListener() {
32 @Override
33 public void onClick(View view) {
34 // fetch data and create note object
35 note = new Note(et_content.getText().toString(),
36 et_title.getText().toString());
37
38 // create worker thread to insert data into database
39 new InsertTask(AddNoteActivity.this,note).execute();
40 }
41 });
42
43}
44
45private void setResult(Note note, int flag){
46setResult(flag,new Intent().putExtra("note",note));
47finish();
48}
49
50private static class InsertTask extends AsyncTask<Void,Void,Boolean> {
51
52 private WeakReference<AddNoteActivity> activityReference;
53 private Note note;
54
55 // only retain a weak reference to the activity
56 InsertTask(AddNoteActivity context, Note note) {
57 activityReference = new WeakReference<>(context);
58 this.note = note;
59 }
60
61 // doInBackground methods runs on a worker thread
62 @Override
63 protected Boolean doInBackground(Void... objs) {
64 activityReference.get().noteDatabase.getNoteDao().insertNote(note);
65 return true;
66 }
67
68 // onPostExecute runs on main thread
69 @Override
70 protected void onPostExecute(Boolean bool) {
71 if (bool){
72 activityReference.get().setResult(note,1);
73 }
74 }
75
76}
77
78}
activity_add_note.xml
1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:layout_margin="4dp"
8 tools:context="com.example.pavneet_singh.roomdemo.AddNoteActivity"
9 android:orientation="vertical">
10
11 <android.support.design.widget.TextInputLayout
12 android:id="@+id/txtInput"
13 android:layout_width="match_parent"
14 android:layout_height="wrap_content">
15 <android.support.design.widget.TextInputEditText
16 android:inputType="text"
17 android:id="@+id/et_title"
18 android:hint="Title"
19 android:singleLine="true"
20 android:layout_width="match_parent"
21 android:layout_height="wrap_content" />
22 </android.support.design.widget.TextInputLayout>
23
24 <LinearLayout
25 android:layout_below="@+id/txtInput"
26 android:orientation="vertical"
27 android:layout_width="match_parent"
28 android:layout_height="match_parent">
29
30 <android.support.design.widget.TextInputLayout
31 android:id="@+id/txtInput2"
32 android:layout_width="match_parent"
33 android:layout_weight="1"
34 android:layout_height="0dp">
35 <android.support.design.widget.TextInputEditText
36 android:id="@+id/et_content"
37 android:hint="Content"
38 android:gravity="top"
39 android:singleLine="false"
40 android:inputType="textMultiLine|textNoSuggestions"
41 android:layout_width="match_parent"
42 android:layout_height="match_parent" />
43 </android.support.design.widget.TextInputLayout>
44
45 <Button
46 android:id="@+id/but_save"
47 android:text="Save"
48 android:layout_gravity="center"
49 android:layout_width="wrap_content"
50 android:layout_height="wrap_content" />
51
52 </LinearLayout>
53
54</RelativeLayout>
Visual result of AddNoteActivity
with above implementation
NoteListActivity.java
1package com.example.pavneet_singh.roomdemo;
2
3import android.content.DialogInterface;
4import android.content.Intent;
5import android.os.AsyncTask;
6import android.os.Bundle;
7import android.support.design.widget.FloatingActionButton;
8import android.support.v7.app.AlertDialog;
9import android.support.v7.app.AppCompatActivity;
10import android.support.v7.widget.LinearLayoutManager;
11import android.support.v7.widget.RecyclerView;
12import android.support.v7.widget.Toolbar;
13import android.view.View;
14import android.widget.TextView;
15
16import com.example.pavneet_singh.roomdemo.adapter.NotesAdapter;
17import com.example.pavneet_singh.roomdemo.notedb.NoteDatabase;
18import com.example.pavneet_singh.roomdemo.notedb.model.Note;
19
20import java.lang.ref.WeakReference;
21import java.util.List;
22
23public class NoteListActivity extends AppCompatActivity implements NotesAdapter.OnNoteItemClick{
24
25private TextView textViewMsg;
26private RecyclerView recyclerView;
27private NoteDatabase noteDatabase;
28private List<Note> notes;
29private NotesAdapter notesAdapter;
30private int pos;
31
32@Override
33protected void onCreate(Bundle savedInstanceState) {
34super.onCreate(savedInstanceState);
35setContentView(R.layout.activity_main);
36initializeVies();
37displayList();
38}
39
40private void displayList(){
41// initialize database instance
42noteDatabase = NoteDatabase.getInstance(NoteListActivity.this);
43// fetch list of notes in background thread
44new RetrieveTask(this).execute();
45}
46
47private static class RetrieveTask extends AsyncTask<Void,Void,List<Note>>{
48
49 private WeakReference<NoteListActivity> activityReference;
50
51 // only retain a weak reference to the activity
52 RetrieveTask(NoteListActivity context) {
53 activityReference = new WeakReference<>(context);
54 }
55
56 @Override
57 protected List<Note> doInBackground(Void... voids) {
58 if (activityReference.get()!=null)
59 return activityReference.get().noteDatabase.getNoteDao().getNotes();
60 else
61 return null;
62 }
63
64 @Override
65 protected void onPostExecute(List<Note> notes) {
66 if (notes!=null && notes.size()>0 ){
67 activityReference.get().notes = notes;
68
69 // hides empty text view
70 activityReference.get().textViewMsg.setVisibility(View.GONE);
71
72 // create and set the adapter on RecyclerView instance to display list
73 activityReference.get().notesAdapter = new NotesAdapter(notes,activityReference.get());
74 activityReference.get().recyclerView.setAdapter(activityReference.get().notesAdapter);
75 }
76 }
77
78}
79
80private void initializeVies(){
81Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
82setSupportActionBar(toolbar);
83textViewMsg = (TextView) findViewById(R.id.tv\_\_empty);
84
85 // Action button to add note
86 FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
87 fab.setOnClickListener(listener);
88 recyclerView = findViewById(R.id.recycler_view);
89 recyclerView.setLayoutManager(new LinearLayoutManager(NoteListActivity.this));
90
91}
92
93}
To update note object, the content of already created object needs to be updated while keeping the same primary key
Here, AddNoteActivity
is being reused to perform update and insertion.
1public class AddNoteActivity extends AppCompatActivity {
2
3 private TextInputEditText et_title,et_content;
4 private NoteDatabase noteDatabase;
5 private Note note;
6 private boolean update;
7
8 @Override
9 protected void onCreate(Bundle savedInstanceState) {
10 super.onCreate(savedInstanceState);
11 setContentView(R.layout.activity_add_note);
12 et_title = findViewById(R.id.et_title);
13 et_content = findViewById(R.id.et_content);
14 noteDatabase = NoteDatabase.getInstance(AddNoteActivity.this);
15 Button button = findViewById(R.id.but_save);
16 if ( (note = (Note) getIntent().getSerializableExtra("note"))!=null ){
17 getSupportActionBar().setTitle("Update Note");
18 update = true;
19 button.setText("Update");
20 et_title.setText(note.getTitle());
21 et_content.setText(note.getContent());
22 }
23
24 button.setOnClickListener(new View.OnClickListener() {
25 @Override
26 public void onClick(View view) {
27 note.setContent(et_content.getText().toString());
28 note.setTitle(et_title.getText().toString());
29 noteDatabase.getNoteDao().updateNote(note);
30 }
31 });
32 }
33
34}
To delete a note object, just invoke the delete
method and provide the note object for deletion as the argument.
noteDatabase.getNoteDao().deleteNote(notes.get(pos));
Do not forget to remove object from list which is being used in NoteListActivity to display list and also notify the adapter using adapterObj.notifyDataSetChanged()
Final result after implementing update and delete
Room offers many other features like LiveData for keeping the data source updated all the time and rxAndroid
support for reactive programming.
Check out the sample application repository here. Hopefully this guide introduced you to a lesser known yet useful form of Android application data storage.
Please post your comments, questions, or suggestions in the discussion section below and favorite this guide if you enjoyed it. Thank you for reading!