Android - Room Persistence Library

Room ဆိုတာဘာလဲ?

Room ဆိုတာက Google I/O 2017 ပွဲမှာ မိတ်ဆက်ခဲ့တဲ့ Android Architecture Components ထဲမှာပါဝင်တဲ့ library တစ်ခုပါ။
Room သုံးတဲ့လူအများစုကတော့ Room Database လို့ခေါ်ကြပါတယ်။
အမှန်တော့ Room က Android မှာသုံးနေကြ SQLite Database ကိုပဲ အပေါ်ကနေ layer တစ်ခုထပ်အုပ်ပေးတဲ့ library တစ်ခုပေ့ါ။
Room persistence library လို့ခေါ်ပါတယ်။

ဘာကြောင့် SQLite ကိုပဲမသုံးဘဲ Room ထပ်သုံးရတာလဲ ?

ကိုယ့်ရဲ့ android app မှာ SQLite ထည့်သုံးဖူးတဲ့သူတွေကတော့ ကြုံဖူးမှာပါ။

SQLite သုံးမယ်ဆိုတာနဲ့ Helper class တွေဆောက်ရမယ်။
Table ဆောက်ဖို့ Query တွေရေးရမယ်။

public class MyDBHandler extends SQLiteOpenHelper {

    private static final int DATABASE_VERSION = 1;
    private static final String DATABASE_NAME = "studentDB.db";
    public static final String TABLE_NAME = "Student";
    public static final String COLUMN_ID = "_id";
    public static final String COLUMN_NAME = "name";

    public MyDBHandler(Context context, Stringname, SQLiteDatabase.CursorFactoryfactory, intversion) {
    super(context, DATABASE_NAME, factory, DATABASE_VERSION);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        String CREATE_TABLE = "CREATE TABLE" + TABLE_NAME + "(" + COLUMN_ID +
        "INTEGER PRIMARYKEY," + COLUMN_NAME + "TEXT )";
        db.execSQL(CREATE_TABLE);
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int i, int i1) {
        ...
    }
    public String loadHandler() {
        ...
    }
    public void addHandler(Student student) {
        ...
    }
    public Student findHandler(String studentname) {
        ...
    }
    public boolean deleteHandler(int ID) {
        ...
    }
    public boolean updateHandler(int ID, String name) {
        ...
    }
}

Table ထဲကို data insert ထည့်ချင်ရင် Column တစ်ခုချင်းဆီ set လုပ်ပေးရတယ်။

public void addHandler(Student student) {
     ContentValues values = new ContentValues();
     values.put(COLUMN_ID, student.getID());
     values.put(COLUMN_NAME, student.getStudentName());
     SQLiteDatabase db = this.getWritableDatabase();
     db.insert(TABLE_NAME, null, values);
     db.close();
}

Cursor တွေသုံးရတယ်။ cursor အပိတ်အဖွင့် db အပိတ်အဖွင့်တွေ ဂရုစိုက်ရတယ်။
select ဆွဲထုတ်ရင်လဲ cursor result ပဲရပြီး ကိုယ်တကယ်လိုချင်တဲ့ Model object ထဲကို တစ်ခုချင်း set လုပ်ပေးရတယ်။

public Student findHandler(String studentname) {
        Stringquery = "Select * FROM " + TABLE_NAME + "WHERE" + COLUMN_NAME + " = " + "'" + studentname + "'";
        SQLiteDatabase db = this.getWritableDatabase();
        Cursor cursor = db.rawQuery(query, null);
        Student student = new Student();
        if (cursor.moveToFirst()) {
             cursor.moveToFirst();
             student.setID(Integer.parseInt(cursor.getString(0)));
             student.setStudentName(cursor.getString(1));
             cursor.close();
        } else {
             student = null;
        }
        db.close();
        return student;
}

Table မှာ column ထပ်ထည့်တာတို့ data type ပြောင်းချင်တာမျိုးရှိလို့ Database migration လုပ်ချင်ရင် တဖြည်းဖြည်းပိုရှုပ်လာမယ်။
cursor တွေ db တွေကို သေချာဂရုစိုက်ပြီး မကိုင်တွယ်ရင် memory leak တွေဖြစ်မယ် ၊ ဖုန်းရဲ့ performance ကိုပါ ထိခိုက်စေမယ်။
Query လုပ်တာတွေကို run time မှ execute တန်းလုပ်တာဖြစ်တဲ့အတွက် compile လုပ်တဲ့အချိန်မှာ error ရှိနေရင် ၊ syntax မှားနေရင် မှားနေမှန်း မသိနိုင်ဘူး။ Error log ဖတ်ရတာကလဲ ရှုပ်ထွေးတယ်။
ဒါတွေအကုန်လုံးကို Room persistence library ကဖြေရှင်းပေးထားပါတယ်။

Comments

  • edited June 7

    Room ကို ဘယ်လိုသုံးလဲ

    1. Room ကို သုံးဖို့အတွက်

    • Google maven repo ကို project ရဲ့ gradle ထဲမှာထည့်ပေးရပါတယ်။
    allprojects {
        repositories {
            jcenter()
            maven { url 'https://maven.google.com' }
        }
    }
    
    • Room persistence library ကို app ရဲ့ gradle dependencies ထဲမှာ ထည့်ပေးရပါတယ်။
    dependencies {
        implementation "android.arch.persistence.room:runtime:1.1.0"
        annotationProcessor "android.arch.persistence.room:compiler:1.1.0"
    }
    

    2. Database စဆောက်ဖို့အတွက် Database class လိုပါတယ်။

    App တစ်ခုလုံးရဲ့ တစ်ခုတည်းသော database ဖြစ်တဲ့အတွက် AppDatabase လို့ class ကိုနာမည်ပေးပြီး RoomDatabase ကို extend လုပ်ရပါတယ်။

    // AppDatabase.java
    @Database(entities = {BookEntity.class}, version = 1)
    public abstract class AppDatabase extends RoomDatabase{
        private static AppDatabase instance;
    
        // Database name
        public static final String DATABASE_NAME = "books-example-db";
    
        // All DAOs of the tables must be added here.
        public abstract BookDao bookDao();
    
        // Checks if an instance already exists, and if not, creates a new instance and returns it.
        public static AppDatabase getInstance(final Context context) {
            if(instance == null){
                instance = buildDatabase(context.getApplicationContext());
            }
            return instance;
        }
    
        private static AppDatabase buildDatabase(final Context appContext) {
            return Room.databaseBuilder(appContext, AppDatabase.class, DATABASE_NAME).build();
        }
    }
    
    • @Database ဆိုတာကတော့ ဒီ class က database class ဖြစ်ကြောင်း define လုပ်ပေးတဲ့ annotation ပါ။

    • အဲ့ဒီ annotation ထဲမှာ entities နဲ့ version ဆိုပြီး parameter ၂ ခုထည့်ပေးရပါတယ်။

    • entities ဆိုတဲ့နေရာမှာ ဒီ database ထဲမှာပါမယ့် table တွေကို Entity class ဆောက်ပြီးထည့်ပေးထားရပါတယ်။ Entity class အကြောင်းကို နောက်မှာ ထပ်ရှင်းပြပါမယ်။

    • version ဆိုတဲ့နေရာမှာတော့ database ရဲ့ version number ကိုထည့်ပေးရပါတယ်။ နောက်ပိုင်း database migration တွေရေးတဲ့အခါမှာ version number ကို လိုက်တိုးပေးရပါတယ်။

    • ပြီးတော့ DAO (Data Access Object) ဆိုတာရှိပါတယ်။ Table/Entity တစ်ခုချင်းဆီမှာ DAO တစ်ခုဆီရှိရပါတယ်။ DAO အကြောင်းကို နောက်မှာ အသေးစိတ် ထပ်ရှင်းပြပါမယ်။

    • ဒီ AppDatabase class ကို ခေါ်သုံးမယ်ဆိုရင် ဒီ class ရဲ့ getInstance() method ကိုခေါ်ပြီးသုံးရပါတယ်။ ဒီလိုမျိုး instance နဲ့ ရေးထားတဲ့ ပုံစံကို Singleton Design Pattern လို့ခေါ်ပါတယ်။

    • Singleton Design Pattern ကို အဓိက သုံးရတဲ့ အကြောင်းကတော့ ဒီ class object ကို ထပ်ခါထပ်ခါ အသစ်မဆောက်မိစေဖို့ပါ။
      Object အသစ်တစ်ခုဆောက်တိုင်းမှာ အဲ့ဒီ Object က memory ထဲမှာ နေရာသွားယူပါတယ်။ ကျွန်တော်တို့က Database ရဲ့ အသွင်းအထုတ် လုပ်ဆောင်မှုတွေ လုပ်တိုင်းမှာ database object အသစ်တစ်ခု ထပ်ဆောက်မယ်ဆိုရင် ဖုန်းရဲ့ memory ကိုမလိုအပ်ဘဲ သုံးရာရောက်ပြီး performance ကို ထိခိုက်စေနိုင်ပါတယ်။

    • Singleton Design Pattern က object အသစ်ကို တိုက်ရိုက်ဆောက်ခွင့်မပေးဘဲ object ကို လာခေါ်ရင် ဒီ object က ဆောက်ပြီးသားရှိလားဆိုတာကို အရင်စစ်ပါတယ်။ ဆောက်ပြီးသားဆိုရင် ဆောက်ပြီးသား object ကို return ပြန်ပေးရင် မဆောက်ရသေးရင်တော့ အသစ်ဆောက်ပြီး return ပြန်ပေးပါတယ်။

  • 3. Entity

    Entity ဆိုတာကတော့ သာမန် Data model တစ်ခုပါပဲ။ ဒီ model ဟာ Entity တစ်ခုဖြစ်ကြောင်းကို @Entity annotation လေးနဲ့ define လုပ်ပေးရုံပါပဲ။
    tableName ဆိုတဲ့ parameter မှာ ဒီ table ကို ပေးချင်တဲ့နာမည်ထည့်ပေးလို့ရပြီး tableName မထည့်ပေးလိုက်ရင်တော့ class name ကို table name အဖြစ် အလိုအလျောက် ဝင်သွားမှာဖြစ်ပါတယ်။

    ဒီ Entity ကို AppDatabase class ရဲ့ Entities ထဲမှာထည့်ပေးလိုက်တာနဲ့ Table အလိုအလျောက်ဆောက်ပေးပြီး id, title, authorName ဆိုတဲ့ attribute တွေက table ရဲ့ column များအဖြစ် အလိုအလျောက်ဝင်သွားမှာဖြစ်ပါတယ်။

    // BookEntity.java
    @Entity(tableName = "books")
    public class BookEntity{
    
        // Primary key must have @PrimaryKey annotation, Use @PrimaryKey(autoGenerate = true) for auto incrementing id.
        @PrimaryKey(autoGenerate = true)
        private int id;
        private String title;
        private String authorName;
    
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
    
        public String getTitle() {
            return title;
        }
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getAuthorName() {
            return authorName;
        }
        public void setAuthorName(String authorName) {
            this.authorName = authorName;
        }
    }
    

    PrimaryKey အဖြစ်ထားချင်တဲ့ field ကို @PrimaryKey ဆိုပြီးတော့ annotation ထည့်ပေးရပြီး အဲ့ဒီ Primary key ကို auto increment လုပ်ထားချင်ရင် autoGenerate = true ဆိုပြီး parameter ထည့်ပေးလိုက်လို့လဲ ရပါတယ်။

    4. DAO (Data Access Object)

    Data Access Object ဆိုတဲ့ နာမည်အတိုင်း DAO ဆိုတာ Entity တစ်ခုချင်းဆီရဲ့ data ကို access လုပ်ဖို့ ( query လုပ်ဖို့ ) အတွက်ဖြစ်ပါတယ်။
    Entity/Table တစ်ခုချင်းဆီတိုင်းမှာ DAO တစ်ခုချင်းဆီ ရှိရပါတယ်။ DAO ကို interface အနေနဲ့ ဆောက်ပေးထားရပြီး DAO ဖြစ်ကြောင်းကို @Dao annotation ထည့်ပေးရပါတယ်။

    @Dao
    public interface BookDao {
        @Query("SELECT * from books")
        List<BookEntity> getAllBooks();
    
        @Query("SELECT * from books where id = :bookId")
        BookEntity getBook(int bookId);
    
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        void addBook(BookEntity bookEntity);
    
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        void addBooks(List<BookEntity> bookEntities);
    
        @Query("DELETE from books")
        void deleteAllBooks();
    }
    
    • Books table ထဲကနေ data တွေ Select လုပ်ချင်ရင် select လုပ်မယ့် method မှာ @Queryဆိုတဲ့ annotation နဲ့အတူ Select လုပ်တဲ့ Query ထည့်ပေးရပါတယ်။ return type ကတော့ record တစ်ခုချင်း select လုပ်ရင်တော့ Entity တစ်ခု return ပြန်ပေးပြီး record အစုလိုက် select လုပ်ရင်တော့ Entity ကို List အနေနဲ့ return ပြန်ပေးပါတယ်။

    • Insert လုပ်ချင်တယ်ဆိုရင် insert လုပ်မယ့် method ကို @Insert annotation ထည့်ပေးရပြီး Query ရေးစရာမလိုတော့ပါဘူး။ Entity object ကိုထည့်ပေးလိုက်တာနဲ့ object ကို table ထဲ အလိုအလျောက်ထည့်ပေးပါတယ်။

    • record တစ်ခုပဲထည့်ချင်ရင် Entity တစ်ခု parameter နဲ့ထည့်ပေးပြီး record အများကြီးကို တစ်ခါထဲထည့်ချင်ရင်တော့ Entity ကို List အနေနဲ့ ထည့်ပေးလိုက်ရုံပါပဲ။

    • OnConflictStrategy.REPLACE ဆိုတာကတော့ insert လုပ်မယ့် record ရဲ့ Primary Key က table ထဲမှာရှိပြီးသားဆိုရင် record အသစ်နဲ့ အစားထိုးမယ်လို့ define လုပ်ထားတာဖြစ်ပါတယ်။
      အစားမထိုးချင်ဘူးဆိုရင်တော့ OnConflictStrategy.IGNORE ကိုသုံးနိုင်ပါတယ်။

    5. စတင်အသုံးပြုခြင်း

    Database ကို Activity ဖြစ်စေ Fragment ကဖြစ်စေ ViewModel ကဖြစ်စေ ခေါ်သုံးတော့မယ်ဆိုရင် AppDatabase.getInstance(context) နဲ့ ခေါ်သုံးလို့ရပါပြီ။ Database အသွင်းအထုတ်တွေလုပ်တဲ့အခါမှာ Room က UI Thread မှာအလုပ်ပေးမလုပ်ပါဘူး။ Data အသွင်းအထုတ်လုပ်တဲ့ အလုပ်က အချိန်ကြာနေခဲ့ရန် အဲ့ဒီအလုပ် မပြီးမချင်း UI က ရပ်နေမှာကြောင့်ပါ။

    အဲ့ဒါကြောင့် database process တွေအကုန်လုံးကို Background Thread မှာ အလုပ်လုပ်ဖို့အတွက် AsyncTask နဲ့သုံးပေးရပါတယ်။

    // ExampleActivity.java
    public class ExampleActivity extends AppCompatActivity {
        private List<BookEntity> bookList;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.example);
    
            new SelectBooksAsyncTask().execute();
        }
        private class SelectBooksAsyncTask extends AsyncTask<Void, Void, Void>{
            @Override
            protected Void doInBackground(Void... voids) { 
                bookList = AppDatabase.getInstance(ExampleActivity.this).bookDao().getAllBooks();
                return null;
            }
        }
    }
    
    • AsyncTask ကရတဲ့ book list ကို UI မှာ မိမိစိတ်ကြိုက် ပြလို့ရပါပြီ။ Background thread မှာ အလုပ်လုပ်ဖို့အတွက် AsyncTask တစ်ခုဆောက်ပေးရတာက အနည်းငယ်တော့ အလုပ်ရှုပ်တယ်။

    • အဲ့ဒါကို ရှင်းဖို့ LiveData နဲ့ Select Query ကို ဘယ်လို လက်ခံမလဲ ၊ LiveData ကို ဘယ်လိုအသုံးပြုမလဲ ဆိုတာတွေကို နောက် Post မှာ သက်သက်ရှင်းပြသွားပါမယ်။

    Android Architecture Components တွေဖြစ်တဲ့ DataBinding, ViewModel, LiveData, Room စတာတွေကို အသုံးပြုထားတဲ့ Sample Project ကို https://gitlab.com/anntphyothwin/android-archi-sample မှာ လေ့လာနိုင်ပါတယ်။

Sign In or Register to comment.