Rest API Pagination with Paging Library.
Bài đăng này đã không được cập nhật trong 6 năm
Vậy paging là gì ? Chính là thư viện phân trang giúp bạn dễ dàng load more data trong ứng dụng của mình sử dụng RecyclerView. Một số ưu điểm của Paging:
- Giả sư chúng ta có 1000 item , nếu như load hết tất cả ra, thì sẽ mất 1 khoảng time delay khá nhiều, nhưng nế như ta chia nhỏ ra, load 10 item 1, như vậy thời gian rút ngắn hơn, tốn ít băng thông mạng hơn, và người dùng có thể trải nghiệm UX tốt hơn.
- Sử dụng đơn giản, linh hoạt . Tối ưu được hiệu năng của device Vậy, sử dụng thư viện này như thế nào ? Khá đơn giản
1.Adding Components vào project
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
}
}
dependencies {
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0-beta01'
annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.0.0-beta01"
// Room
implementation 'androidx.room:room-runtime:2.0.0-beta01'
annotationProcessor "androidx.room:room-compiler:2.0.0-beta01"
// Paging
implementation 'androidx.paging:paging-runtime:2.0.0-beta01'
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0-beta01'
implementation 'androidx.appcompat:appcompat:1.0.0-beta01'
}
2.Setting up Retrofit cho Pagination
public interface GitHubService {
@GET("/users")
Call<List<User>> getUser(@Query("since") int since, @Query("per_page") int perPage);
}
Ta tạo ra 1 lớp RetrofitService static để config cho retrofit
public class GitHubApi {
public static GitHubService createGitHubService() {
Retrofit.Builder builder = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.github.com");
return builder.build().create(GitHubService.class);
}
}
3.Create ItemKeyedDataSource
Trình tải dữ liệu gia tăng để phân trang nội dung có khóa, trong đó nội dung được tải sử dụng các mục đã tải trước đó làm đầu vào cho các lần tải trong những lần tải tiếp theo. Các bạn có thể tham khảo thêm về các loại DataSource : https://developer.android.com/reference/android/arch/paging/ItemKeyedDataSource
public class ItemKeyedUserDataSource extends ItemKeyedDataSource<Long, User> {
public static final String TAG = "ItemKeyedUserDataSource";
GitHubService gitHubService;
LoadInitialParams<Long> initialParams;
LoadParams<Long> afterParams;
private MutableLiveData networkState;
private MutableLiveData initialLoading;
private Executor retryExecutor;
public ItemKeyedUserDataSource(Executor retryExecutor) {
gitHubService = GitHubApi.createGitHubService();
networkState = new MutableLiveData();
initialLoading = new MutableLiveData();
this.retryExecutor = retryExecutor;
}
public MutableLiveData getNetworkState() {
return networkState;
}
public MutableLiveData getInitialLoading() {
return initialLoading;
}
@Override
public void loadInitial(@NonNull LoadInitialParams<Long> params, @NonNull LoadInitialCallback<User> callback) {
Log.i(TAG, "Loading Rang " + 1 + " Count " + params.requestedLoadSize);
List<User> gitHubUser = new ArrayList();
initialParams = params;
initialLoading.postValue(NetworkState.LOADING);
networkState.postValue(NetworkState.LOADING);
gitHubService.getUser(1, params.requestedLoadSize).enqueue(new Callback<List<User>>() {
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
if (response.isSuccessful() && response.code() == 200) {
gitHubUser.addAll(response.body());
callback.onResult(gitHubUser);
initialLoading.postValue(NetworkState.LOADED);
networkState.postValue(NetworkState.LOADED);
initialParams = null;
} else {
Log.e("API CALL", response.message());
initialLoading.postValue(new NetworkState(Status.FAILED, response.message()));
networkState.postValue(new NetworkState(Status.FAILED, response.message()));
}
}
@Override
public void onFailure(Call<List<User>> call, Throwable t) {
String errorMessage;
errorMessage = t.getMessage();
if (t == null) {
errorMessage = "unknown error";
}
networkState.postValue(new NetworkState(Status.FAILED, errorMessage));
}
});
}
@Override
public void loadAfter(@NonNull LoadParams<Long> params, @NonNull LoadCallback<User> callback) {
Log.i(TAG, "Loading Rang " + params.key + " Count " + params.requestedLoadSize);
List<User> gitHubUser = new ArrayList();
afterParams = params;
networkState.postValue(NetworkState.LOADING);
gitHubService.getUser(params.key, params.requestedLoadSize).enqueue(new Callback<List<User>>() {
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
if (response.isSuccessful()) {
gitHubUser.addAll(response.body());
callback.onResult(gitHubUser);
networkState.postValue(NetworkState.LOADED);
afterParams = null;
} else {
networkState.postValue(new NetworkState(Status.FAILED, response.message()));
Log.e("API CALL", response.message());
}
}
@Override
public void onFailure(Call<List<User>> call, Throwable t) {
String errorMessage;
errorMessage = t.getMessage();
if (t == null) {
errorMessage = "unknown error";
}
networkState.postValue(new NetworkState(Status.FAILED, errorMessage));
}
});
}
@Override
public void loadBefore(@NonNull LoadParams<Long> params, @NonNull LoadCallback<User> callback) {
}
@NonNull
@Override
public Long getKey(@NonNull User item) {
return item.userId;
}
}
ta thực hiện load trong hàm loadInitial và loadAfter để load data
4.DataSourceFactory
Tạo ra 1 lớp DataSourceFactory để xử lý vấn đề về config và data
public class UserViewModel extends ViewModel {
public LiveData<PagedList<User>> userList;
public LiveData<NetworkState> networkState;
Executor executor;
LiveData<ItemKeyedUserDataSource> tDataSource;
public UserViewModel() {
GitHubUserDataSourceFactory githubUserDataSourceFacteory = new GitHubUserDataSourceFactory(executor);
tDataSource = githubUserDataSourceFacteory.getMutableLiveData();
networkState = Transformations.switchMap(githubUserDataSourceFacteory.getMutableLiveData(), dataSource -> {
return dataSource.getNetworkState();
});
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder()).setEnablePlaceholders(false)
.setInitialLoadSizeHint(10)
.setPageSize(20).build();
userList = (new LivePagedListBuilder(githubUserDataSourceFacteory, pagedListConfig))
.build();
}
}
5. PagelistAdapter
Ta sẽ tạo ra 1 pagelistAdapter để tiến hành phân trang, dùng diffCallback để differnce 2 element
public class UserAdapter extends PagedListAdapter<User, RecyclerView.ViewHolder> {
private static final String TAG = "UserAdapter";
private NetworkState networkState;
private ListItemClickListener itemClickListener;
public UserAdapter(ListItemClickListener itemClickListener) {
super(DIFF_CALLBACK);
this.itemClickListener = itemClickListener;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view;
if (viewType == R.layout.item_user_list) {
view = layoutInflater.inflate(R.layout.item_user_list, parent, false);
return new UserItemViewHolder(view);
} else if (viewType == R.layout.network_state_item) {
view = layoutInflater.inflate(R.layout.network_state_item, parent, false);
return new NetworkStateItemViewHolder(view, itemClickListener);
} else {
throw new IllegalArgumentException("unknown view type");
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (getItemViewType(position)) {
case R.layout.item_user_list:
((UserItemViewHolder) holder).bindTo(getItem(position));
break;
case R.layout.network_state_item:
((NetworkStateItemViewHolder) holder).bindView(networkState);
break;
}
}
private boolean hasExtraRow() {
if (networkState != null && networkState != NetworkState.LOADED) {
return true;
} else {
return false;
}
}
@Override
public int getItemViewType(int position) {
if (hasExtraRow() && position == getItemCount() - 1) {
return R.layout.network_state_item;
} else {
return R.layout.item_user_list;
}
}
public void setNetworkState(NetworkState newNetworkState) {
....
}
public static DiffUtil.ItemCallback<User> DIFF_CALLBACK = new DiffUtil.ItemCallback<User>() {
@Override
public boolean areItemsTheSame(@NonNull User oldItem, @NonNull User newItem) {
return oldItem.userId == newItem.userId;
}
@Override
public boolean areContentsTheSame(@NonNull User oldItem, @NonNull User newItem) {
return oldItem.equals(newItem);
}
};
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
User user = (User) obj;
return user.userId == this.userId && user.firstName == this.firstName;
}
}
Sau đó chỉ cần gọi :
RecyclerView recyclerView = findViewById(R.id.userList);
LinearLayoutManager llm = new LinearLayoutManager(this);
llm.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(llm);
UserViewModel viewModel = ViewModelProviders.of(this).get(UserViewModel.class);
viewModel.init(userDao);
final UserAdapter userUserAdapter = new UserAdapter();
viewModel.userList.observe(this, pagedList -> {
userUserAdapter.submitList(pagedList);
});
viewModel.networkState.observe(this, networkState -> {
userUserAdapter.setNetworkState(networkState);
Log.d(TAG, "Network State Change");
});
recyclerView.setAdapter(userUserAdapter);
để init data cho adapter và receycler view các bạn có thể tham khảo code tai: https://github.com/Thumar/PagingWithRestAPI bào viết được tham khảo tại: http://androidkt.com/rest-api-pagination-paging-library/
All rights reserved