iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 7
0

昨天將Data Binding做完整之後View就處理得差不多了,之後會朝調整ViewModel和在Model新增資料庫為主。

今天要講的是LiveData和Transformations的搭配使用,很多時候我們在撈資料時都會有一個key值,例如使用者id、商品id或是本篇的repository名稱等等,而Transformations就能讓LiveData更好的處理這種情境。

LiveData in Model

目前撈資料的流程是Model用callback來回傳內容給ViewModel,我們將callback改成直接回傳LiveData,這樣Model -> ViewModel -> View就一路都用LiveData傳遞,也與我們的藍圖GithubBrowserSample相同。

修改DataModel

public class DataModel {

    ...

    public MutableLiveData<List<Repo>> searchRepo(String query) {
        final MutableLiveData<List<Repo>> repos = new MutableLiveData<>();
        githubService.searchRepos(query)
                .enqueue(new Callback<RepoSearchResponse>() {
                    @Override
                    public void onResponse(@NonNull Call<RepoSearchResponse> call, @NonNull Response<RepoSearchResponse> response) {
                        repos.setValue(response.body().getItems());
                    }

                    ...
                });
        return repos;
    }
}

在searchRepo中建立新的MutableLiveData並回傳,稍後連線完成時會在onResponse中setValue()更新此LiveData的內容。

那RepoViewModel的部分呢,如果你改成這樣:

public class RepoViewModel extends ViewModel {

    ...

    MutableLiveData<List<Repo>> searchRepo(String query) {
        // NO!
        return dataModel.searchRepo(query);
    }
}

以上寫法是由於DataModel已經準備好LiveData了,貌似ViewModel可以不用再處理直接交給View,但這樣有兩個問題:

  1. 每次改關鍵字再搜尋時都會到DataModel拿新的LiveData,這樣View每次都要換去observe新的LiveData。
  2. 當View recreate的時候,會再次呼叫serchRepo導致DataModel又連線一次。

這時就要用到Transformations了!

Transformations switchMap

switchMap方法是將會改變的key值也寫成LiveData並作為trigger,以本篇來而言此trigger即為搜尋關鍵字query,當此trigger的value改變時便會觸發令主要資料的LiveData更新value而不用回傳新的instance,直接看程式碼比較清楚:

public class RepoViewModel extends ViewModel {

    ...
    
    private final MutableLiveData<String> query = new MutableLiveData<>();

    private final LiveData<List<Repo>> repos;
    
    public RepoViewModel(final DataModel dataModel) {
        ...
        repos = Transformations.switchMap(query, new Function<String, LiveData<List<Repo>>>() {
            @Override
            public LiveData<List<Repo>> apply(String userInput) {
                return dataModel.searchRepo(userInput);
            }
        });
    }

    ...
    
    void searchRepo(String userInput) {
        query.setValue(userInput);
    }
}

我們將query當成trigger,並提供searchRepo(String userInput)來更新它的value,每當query更新的時候就會觸發switchMap中的Function使repos自DataModel接收到新的資料。

Transformations不會使LiveData變成新的instance,因此View一直都是對repos做observe,即解決了上面提到的兩個問題。

另外google sample中做了一個AbsentLiveData來協助檢查trigger是否為null

/**
 * A LiveData class that has {@code null} value.
 */
public class AbsentLiveData extends LiveData {
    private AbsentLiveData() {
        postValue(null);
    }
    public static <T> LiveData<T> create() {
        //noinspection unchecked
        return new AbsentLiveData();
    }
}

其內容是一個會post null value的LiveData,用來避免trigger是null時撈資料可能會出錯。

於switchMap中使用:

repos = Transformations.switchMap(query, new Function<String, LiveData<List<Repo>>>() {
    @Override
    public LiveData<List<Repo>> apply(String userInput) {
        if (TextUtils.isEmpty(userInput)) {
            return AbsentLiveData.create();
        } else {
            return dataModel.searchRepo(userInput);
        }
    }
});

當userInput的值為空時回傳AbsentLiveData,否則就跟model請求資料。

Transformations map

map是將作為關鍵字的LiveData當成source,並用source的value轉換成主要資料,寫法跟switchMap很相似:

repos = Transformations.map(query, new Function<String, List<Repo>>() {
    @Override
    public List<Repo> apply(String input) {
        // do something
    }
});

因為我們專案沒有map的需求所以上面例子也許不太能體會,看看官方文件的例子:

LiveData userLiveData = ...;
LiveData userName = Transformations.map(userLiveData, new Function<User, String>() {
    @Override
    public String apply(User user) {
        return user.firstName + " " + user.lastName;
    }
});

第一個userLiveData作為source,當它的value改變時會觸發Function讓我們將user的firstName加上lastName作為LiveData userName的value。


總結來說,當你需要以某個key值來更新LiveData的value時可以優先考慮用Transformations來達成需求。

GitHub source code:
https://github.com/IvanBean/ITBon2018/tree/day07-livedata-transformations


上一篇
Data Binding with RecyclerView & Custom setter
下一篇
LiveData整合API資料與連線狀態
系列文
Android Architecture30

尚未有邦友留言

立即登入留言