看完 CodeLab 的範例,我試著跟著做做看,來體驗 Live Data 的強大吧~~
LiveData 與 Room 的 Gradle
// Room components
implementation "android.arch.persistence.room:runtime:1.1.1"
kapt "android.arch.persistence.room:compiler:1.1.1"
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
// Lifecycle components
implementation "android.arch.lifecycle:extensions:1.1.1"
kapt "android.arch.lifecycle:compiler:1.1.1"
implementation 'com.android.support:recyclerview-v7:26.1.0'
implementation 'com.android.support:design:26.1.0'
使用 Room 搭配 Live Data
首先先 使用 Room 建立 SQLite。
接著 Create Repository,Repository 的職責是存取資料,可能是從 網路、資料庫 存取資料。
有 Repository 可以分隔 操作資料的行為。
class Repository {
private var wordDao: WordDao? = null
private var mAllWords: LiveData<List<Word>>? = null
constructor(context: Context) {
var db = WordRoomDatabase.getInstance(context)
wordDao = db.getWordDao()
mAllWords = wordDao!!.getAllWords()
}
fun insert(word: Word) {
insertAsyncTask(wordDao!!).execute(word)
}
fun getAllWords(): LiveData<List<Word>> {
return mAllWords!!
}
companion object {
private class insertAsyncTask : AsyncTask<Word, Unit, Unit> {
private var asyncTaskWordDao: WordDao
constructor(dao: WordDao) {
asyncTaskWordDao = dao
}
override fun doInBackground(vararg params: Word?) {
asyncTaskWordDao.insert(params[0]!!)
}
}
}
}
ViewModel 是持有資料的物件,也就是說要在 UI 顯示資料的話,都必須跟他拿,或者要 insert 也需要透過他,他會與 Reposity 溝通。
class WordViewModel : AndroidViewModel {
private var wordRepo: Repository
private var mAllWords: LiveData<List<Word>>
constructor(application: Application) : super(application) {
wordRepo = Repository(application)
mAllWords = wordRepo.getAllWords()
}
fun getAllWords(): LiveData<List<Word>> {
return mAllWords
}
fun insert(word: Word) {
wordRepo.insert(word)
}
}
MainActivity 會與 ViewModel 溝通,因為要拿取資料,由於資料的型態是 LiveData,所以有 observe 的方法,當資料有所變動的時候 就會呼叫到 onChanged 接著就更新 UI。
var adapter = MyAdapter()
mWordViewModel = ViewModelProviders.of(this).get(WordViewModel::class.java)
mWordViewModel.getAllWords().observe(this, object : Observer<List<Word>> {
override fun onChanged(t: List<Word>?) {
// Toast.makeText(this@MainActivity, "Call", Toast.LENGTH_LONG).show()
adapter.setWords(t!!)
}
})
recyclerList.layoutManager = LinearLayoutManager(this)
recyclerList.adapter = adapter
LiveData 的好處,當 Activity 被 Destroy 的時候,LiveData 不會造成 Memory leak。
Transformations.map
簡單來說就是 LiveData 變動了就會觸發這個 map 去做對應的 事情。
這個東西可以配合 LiveData 使用,使用方式就像這樣
Transformations.map(queryLiveData, {
reposiory.search(it)
})
那麼他是怎麼跑的呢?
看一下 source code 吧
@MainThread
public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
@NonNull final Function<X, Y> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
public void onChanged(@Nullable X x) {
result.setValue(func.apply(x));
}
});
return result;
}
看到這裡,也就是說 傳入的 LiveData 發生資料變動的時候,會觸發
func.apply(x),進而將變動的資料傳入一開始定義的 function( {
reposiory.search(it)} ),那這個 function 又回傳什麼東西呢?
new Observer<X>() {
@Override
public void onChanged(@Nullable X x) {
result.setValue(func.apply(x));
}
}
Function 的 source code,這個 function 的 apply 會回傳 一個 Generic 那這個 Type 就是看你想回傳啥 Type 就回傳啥 Type。
public interface Function<I, O> {
/**
* Applies this function to the given input.
*
* @param input the input
* @return the function result.
*/
O apply(I input);
Transformations.switchMap
switchMap 與 Map 最大的差別是在 function,switchMap 裡面的 function 已經定義了 一定要回傳 LiveData,使用上並無啥差別~~
@NonNull final Function<X, LiveData<Y>> func@MainThread
public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
@NonNull final Function<X, LiveData<Y>> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(trigger, new Observer<X>() {
LiveData<Y> mSource;
@Override
public void onChanged(@Nullable X x) {
LiveData<Y> newLiveData = func.apply(x);
if (mSource == newLiveData) {
return;
}
if (mSource != null) {
result.removeSource(mSource);
}
mSource = newLiveData;
if (mSource != null) {
result.addSource(mSource, new Observer<Y>() {
@Override
public void onChanged(@Nullable Y y) {
result.setValue(y);
}
});
}
}
});
return result;
}
使用
val repos: LiveData<List<Repo>> = Transformations.switchMap(repoResult, { it ->
it.data
})
val networkErrors: LiveData<String> = Transformations.switchMap(repoResult, { it -> it.networkErrors })