iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 13
0
Software Development

Android Architecture系列 第 13

Architecture Components - Room

  • 分享至 

  • xImage
  •  

今天要介紹Architecture Components中的資料庫library Room,它能讓我們大量減少使用SQLiteDatabase要寫的語法量,並且有兩點優於其他資料庫library:

  1. 在compile時就檢查SQL查詢語法有無錯誤。
  2. 跟其他Architecture Components像是LiveData完全整合。

這幾天我們就將Room加入專案中,儲存從API抓回來的資料達到離線時也能檢視cache資料的效果。

Room

1.加入dependencies

implementation "android.arch.persistence.room:runtime:1.0.0"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0"

2.建立Entity,即我們的table schema
我們要儲存搜尋的關鍵字及其搜尋結果,先建立儲存關鍵字的Entity:

@Entity
@TypeConverters(GithubTypeConverters.class)
public class RepoSearchResult {
    @NonNull
    @PrimaryKey
    public final String query;
    
    public final List<Integer> repoIds;
    
    public final int totalCount;

    public RepoSearchResult(@NonNull String query, List<Integer> repoIds, int totalCount) {
        this.query = query;
        this.repoIds = repoIds;
        this.totalCount = totalCount;
    }
}

建立POJO之後標上@Entity,Room就會將其建成table;@PrimaryKey標示query為主鍵;@TypeConverters是用在repoIds這個欄位把多個整數id合起來變成用逗號分隔的字串,透過TypeConverter可以把List存在一個欄位中,待會再看它是怎麼運作的。

repoIds於資料表中的樣子:
https://ithelp.ithome.com.tw/upload/images/20180102/20103849lFkmm1Y2ba.png

另外一個Entity就是我們的Repo,POJO之前就建好了,只要加上annotation:

@Entity(indices = {@Index("id"), @Index("owner_login")},
        primaryKeys = {"name", "owner_login"})
public class Repo {

    ...
    
    @Embedded(prefix = "owner_")
    public final Owner owner;

    ...
    }
}

@Entity中使用primaryKeys表示要由多欄位當主鍵,@Index是索引,而其中有個owner_login,那是藉由@Embedded我們把Owner也加入table欄位中。

Repo資料表完成樣:
https://ithelp.ithome.com.tw/upload/images/20180102/201038494kz66fMXJ3.png

最後兩個欄位login和url原本是Owner的欄位而不是Repo的,但藉由@Embedded我們把它們欄位合併起來,並用(prefix = "owner_")讓login變成owner_login,url亦同。

最後是剛剛提到的TypeConverter把List轉成String存在同一個欄位,如下:

public class GithubTypeConverters {
    @TypeConverter
    public static List<Integer> stringToIntList(String data) {
        if (data == null) {
            return Collections.emptyList();
        }
        return StringUtil.splitToIntList(data);
    }

    @TypeConverter
    public static String intListToString(List<Integer> ints) {
        return StringUtil.joinIntoString(ints);
    }
}

將method標上@TypeConverter,Room就會在insert/query時自動套用。例如要儲存List{1,2,3,4},Room會找出輸入參數型態為List<Integer>intListToString,將List轉成字串"1,2,3,4"並存在repoIds欄位,之後query時因為repoIds的型態是List<Integer>,就會套用return type為List<Integer>stringToIntList,把字串再轉回List成為查詢結果。

TypeConverter的部分過兩天講Room的資料關聯會再多說明,先往下進行。

3.建立DAO(Data Access Object)
DAO是包裝SQL語法的class,將CRUD都寫在這邊。

@Dao
public abstract class RepoDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public abstract void insertRepos(List<Repo> repositories);

    @Query("SELECT * FROM Repo WHERE id in (:repoIds)")
    protected abstract List<Repo> loadById(List<Integer> repoIds);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public abstract void insert(RepoSearchResult result);

    @Query("SELECT * FROM RepoSearchResult WHERE query = :query")
    public abstract RepoSearchResult search(String query);
}

建立abstract class並標上@Dao,裡面可以用@Insert@Update@Delete以及@Query寫SQL語法。

4.建立Database
這是最後步驟了,首先建立繼承RoomDatabase的abstract class:

@Database(entities = {RepoSearchResult.class, Repo.class}, version = 1)
public abstract class GithubDb extends RoomDatabase {
    abstract public RepoDao repoDao();
}

@Database處列出Entity和資料庫版本,class內容則須包含要用到的DAO。

接著要初始化,因為我們有使用Dagger所以可以寫在Module裡,如果沒有的話就寫在Application中。

@Module(includes = ViewModelModule.class)
class AppModule {
    @Provides
    @Singleton
    GithubDb provideDb(GithubApp app) {
        return Room.databaseBuilder(app, GithubDb.class,"github.db").build();
    }

    @Provides
    @Singleton
    RepoDao provideRepoDao(GithubDb db) {
        return db.repoDao();
    }
    
    ...
}

完成,可以開始用DAO來存取資料。

public void insertResult(RepoSearchResult result) {
    repoDao.insert(result);
}

public RepoSearchResult searchLocal(String query) {
    return repoDao.search(query);
}

不過這樣執行的話會報錯,為了避免資料讀寫造成UI卡頓,Room只允許在background thread進行資料讀寫,所以要用非同步的方式例如Handler或AsyncTask來執行。另外一個不建議使用的方式是在Room的builder加上allowMainThreadQueries(),不過這樣就違背Room設計的本意了。

使用Room之後,我們的App現在有兩個資料來源:Remote API和Local database,明天會修改Model將兩者整合,讓使用者能最快速的取得資料。


GitHub source code:
https://github.com/IvanBean/ITBon2018/tree/day13-initialize-room

Reference:
7 Steps To Room


上一篇
Dependency Injection with Dagger2: Part 4
下一篇
套用Repository Pattern完成最後架構
系列文
Android Architecture30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言