Architecture Components中還有一個比較晚登場的成員Paging,目前還在alpha階段所以沒有規劃進專案中,於今天單獨介紹它。
考量Android裝置的螢幕通常不會太大,App即便有上千筆資料其實同時只會顯示十幾筆,在撈資料時如果處理不好就會造成效能的浪費。
Paging讓我們可以從大量資料先撈出一小部分顯示,並依照滑動列表等情況自動在background thread撈出新的資料更新UI,降低效能和記憶體的消耗。同時Paging也和其他Architecture Components成員整合,一起使用時只要改很少的程式就能套用。
Paging由幾個class組成,簡略說明如下:
LiveData<PagedList>
。運作流程:
Paging Library components
我說明能力不好所以上面都看不懂也沒關係,我們直接看程式,先加入dependencies:
implementation "android.arch.paging:runtime:1.0.0-alpha4-1"
修改DAO:
@Query("SELECT * FROM Repo WHERE id in (:repoIds)")
public abstract DataSource.Factory<Integer, Repo> loadById(List<Integer> repoIds);
Room會自動透過DataSource.Factory
產生PositionalDataSource,這種DataSource讓我們從任意位置開始撈取資料,例如撈出第1000筆之後的50筆。
修改Repository:
public class RepoRepository {
...
public LiveData<Resource<PagedList<Repo>>> search(final String query) {
return new NetworkBoundResource<PagedList<Repo>, RepoSearchResponse>() {
@NonNull
@Override
protected LiveData<PagedList<Repo>> loadFromDb() {
return Transformations.switchMap(repoDao.search(query), new Function<RepoSearchResult, LiveData<PagedList<Repo>>>() {
@Override
public LiveData<PagedList<Repo>> apply(RepoSearchResult searchData) {
return new LivePagedListBuilder<>(repoDao.loadById(searchData.repoIds), 30).build();
}
});
}
@Override
protected boolean shouldFetch(@Nullable PagedList<Repo> data) {
return data == null;
}
...
}.asLiveData();
}
}
原本回傳型態由List<Repo>
改成PagedList<Repo>
,loadFromDb中使用LivePagedListBuilder
將撈出來的PagedList用LiveData包裝變成LiveData<PagedList<Repo>>
,30是要撈的資料數量(PageSize)。
LivePagedListBuilder
可以用config做更多設定,例如:
PagedList.Config config = new PagedList.Config.Builder()
.setPageSize(30)
.setPrefetchDistance(20)
.setEnablePlaceholders(true)
.build();
LivePagedListBuilder<>(repoDao.loadById(searchData.repoIds), config).build();
若沒有用config設定而是像Repository中直接寫PageSize數字的話,PrefetchDistance默認等於PageSize。
ViewModel只要改LiveData的資料型態就好囉,將原本的List<Repo>
改成PagedList<Repo>
:
public class RepoViewModel extends ViewModel {
private final LiveData<Resource<PagedList<Repo>>> repos;
...
@Inject
public RepoViewModel(RepoRepository repoRepository) {
...
repos = Transformations.switchMap(query, new Function<String, LiveData<Resource<PagedList<Repo>>>>() {
@Override
public LiveData<Resource<PagedList<Repo>>> apply(String userInput) {
...
}
});
}
LiveData<Resource<PagedList<Repo>>> getRepos() {
return repos;
}
}
建立PagedListAdapter:
public class RepoAdapter extends PagedListAdapter<Repo, RepoAdapter.RepoViewHolder> {
RepoAdapter() {
super(DIFF_CALLBACK);
}
class RepoViewHolder extends RecyclerView.ViewHolder{
private final RepoItemBinding binding;
RepoViewHolder(RepoItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void bind(Repo repo) {
binding.setRepo(repo);
binding.executePendingBindings();
}
}
@Override
public RepoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
RepoItemBinding binding = RepoItemBinding.inflate(layoutInflater, parent, false);
return new RepoViewHolder(binding);
}
@Override
public void onBindViewHolder(RepoViewHolder holder, int position) {
Repo repo = getItem(position);
holder.bind(repo);
}
private static final DiffCallback<Repo> DIFF_CALLBACK = new DiffCallback<Repo>() {
@Override
public boolean areItemsTheSame(@NonNull Repo oldRepo, @NonNull Repo newRepo) {
return Objects.equals(oldRepo.id, newRepo.id);
}
@Override
public boolean areContentsTheSame(@NonNull Repo oldRepo, @NonNull Repo newRepo) {
return Objects.equals(oldRepo.name, newRepo.name) &&
Objects.equals(oldRepo.description, newRepo.description);
}
};
}
需建立DIFF_CALLBACK
,這像是精簡版的DiffUtil,比較好寫之外還會自動在background thread計算資料差異。原本的getItemCount()
那些不用寫了,PagedListAdapter會幫我們處理,並提供內建的method如getItem(position)
讓我們取得Repo,整體比RecyclerView Adapter還簡潔。
最後於View中接收資料:
viewModel.getRepos().observe(this, new Observer<Resource<PagedList<Repo>>>() {
@Override
public void onChanged(@Nullable Resource<PagedList<Repo>> resource) {
...
repoAdapter.setList(resource.data);
}
});
使用PagedListAdapter提供的setList(PagedList)
就會觸發Adapter計算資料差異並更新UI。
Paging目前還在alpha-4階段,之後除了資料庫分頁之外也會支援網路連線的分頁,有相關需求的話可再持續追蹤。現在就導入專案的話要稍留意後續的API異動,更新時有可能會有deprecated的內容。
GitHub source code:
https://github.com/IvanBean/ITBon2018/tree/day23-paging