Reputation: 47
I am new to android development and trying to build the UI for my application. The app integrates with REST backend which accepts a search query and a list of items as response.
interface RetrofitEndpoint {
@GET("paged/list/endpoint")
Call<PagedList<Object>> getPagedList(@Query("query") String query, @Query("pageSize") int pageSize, Query("pageOffset") int pageOffset);
}
The UI displays one item at a time to the user.
I am loading the list into a recyclerview
public class SomeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Object> list;
// .. other overridden members
public void setList(List<Object> list) {
this.list = list;
notifyDataSetChanged();
}
public void addAll(List<Object> newList) {
int lastIndex = list.size() - 1;
list.addAll(newList);
notifyItemRangeInserted(lastIndex, newList.size());
}
}
The part that I am not able to figure out is how do I load more data when I reach the end(or before to avoid latency) of my recyclerview, is there any library/API that does this?
Upvotes: 0
Views: 2588
Reputation: 321
For paged list to work properly it requires bit more stuff in your app.This implementation uses view model, live data and room persistence so it works offline.
your build.gradle:
// ViewModel
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
def paging_version = "2.1.2"
implementation "androidx.paging:paging-runtime:$paging_version"
Retrofit api:
interface RetrofitEndpoint {
@GET("paged/list/endpoint")
Call<List<YourObject>> getYourObjectList(@Query("query") String query, @Query("pageSize") int pageSize, Query("pageOffset") int pageOffset);
}
YourObject:
@Entity(tableName = "your_object")
public class YourObject implements Serializable {
@PrimaryKey(autoGenerate = true)
private int db_id;
...
Dao:
@Dao
public interface YourObjectDao{
/**
* Get your objects from the table.
* -------------------------------
* We get update every time the database update.
*
*
* @return your object from the table
*/
@Insert
void insert(YourObject yourObject);
@Insert
void insertList(List<YourObject> yourObjectList);
@Query("SELECT * FROM your_object")
DataSource.Factory<Integer, YourObject> getAllResults();
@Query("DELETE FROM your_object")
void deleteAll();
}
Database:
@androidx.room.Database(entities = {YourObject.class}, version = 1)
public abstract class Database extends RoomDatabase {
private static Database instance;
public abstract YourObjectDao get_your_object_dao();
public static synchronized Database getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context.getApplicationContext(),
Database.class, DATABASE_NAME)
.fallbackToDestructiveMigration()
.addCallback(roomCallback)
.build();
}
return instance;
}
}
YourObjectBoundaryCallback:
public class YourObjectBoundaryCallback extends PagedList.BoundaryCallback<YourObject> {
private AppExecutors executors;
private Database database;
private YourObjectDao dao;
private Integer page_number;
public YourObjectBoundaryCallback (Application application, AppExecutors executors) {
//super();
this.executors = executors;
database = Database.getInstance(application);
dao = database.get_your_object_dao();
page_number=1;
}
@Override
public void onZeroItemsLoaded() {
super.onZeroItemsLoaded();
Log.d("log", "yourObjects onzeroitemsloaded");
fetchYourObjects(page_number);
}
@Override
public void onItemAtFrontLoaded(@NonNull YourObject itemAtFront) {
super.onItemAtFrontLoaded(itemAtFront);
Log.d("log", "yourObjects onItemAtFrontLoaded");
}
@Override
public void onItemAtEndLoaded(@NonNull YourObject itemAtEnd) {
super.onItemAtEndLoaded(itemAtEnd);
Log.d("log", "yourObjects onItemAtEndLoaded");
page_number=page_number+1;
fetchYourObjects(page_number);
}
public void fetchYourObjects(int pageNumber) {
RetrofitApi retrofitApi = RetrofitInstance.getRetrofitEndpoint();
Call<List<YourObject>> call = retrofitApi.getYourObjectList(query, pageSize,pageNumber);
call.enqueue(new Callback<List<YourObject>>() {
@Override
public void onResponse(Call<List<YourObject>> call, Response<List<YourObject>> response) {
if (!response.isSuccessful()) {
Log.d("log", "YourObjects Response unsuccesful: " + response.code());
return;
}
Log.d("log", "YourObjects Response ok: " + response.code());
List<YourObject> yourObjectsList = response.body();
insertListToDb(yourObjectsList );
}
@Override
public void onFailure(Call<List<YourObject>> call, Throwable t) {
Log.d("log", "yourObjects onFailure: " + t.getMessage());
}
});
}
public void insertListToDb(List<YourObject> list) {
Runnable runnable = () -> {
dao.insertList(list);
};
Runnable diskRunnable = () -> database.runInTransaction(runnable);
executors.diskIO().execute(diskRunnable);
}
}
YourObjects Repository:
public class YourObjectsRepository {
private LiveData<PagedList<YourObject>> yourObjectsPagedList;
private YourObjectBoundaryCallback yourObjectsBoundaryCallback;
private AppExecutors executors;
public YourObjectsRepository (Application application, AppExecutors executors) {
this.executors = executors;
Database database = Database.getInstance(application);
YourObjectDao dao = database.get_your_object_dao();
yourObjectsBoundaryCallback= new YourObjectBoundaryCallback (application, executors);
createYourObjectsPagedList(dao );
}
//this is configuration for your paged list, adjust per your requirements
private PagedList.Config getPagedListConfig(){
return (new PagedList.Config.Builder())
.setEnablePlaceholders(false)
.setPrefetchDistance(40)
.setInitialLoadSizeHint(60)
.setPageSize(20).build();
}
private void createYourObjectsPagedList(YourObjectDao dao){
yourObjectsPagedList= new LivePagedListBuilder<>(dao.getAllResults(), getPagedListConfig())
.setBoundaryCallback(yourObjectsBoundaryCallback).setFetchExecutor(executors.networkIO())
.build();
}
public LiveData<PagedList<YourObject>> getYourObjectsPagedList() {
return yourObjectsPagedList;
}
}
YourObjectsViewModel:
public class YourObjectsViewModel extends AndroidViewModel {
private YourObjectsRepository repo;
public YourObjectsViewModel (@NonNull Application application) {
super(application);
AppExecutors executors = new AppExecutors();
repo= new YourObjectsRepository (application, executors);
}
public LiveData<PagedList<YourObject>> getYourObjectsPagedList() {
return repo.getYourObjectsPagedList();
}
}
AppExecutors:
public class AppExecutors {
private final Executor diskIO;
private final Executor networkIO;
private final Executor mainThread;
private final Executor others;
private final Executor paging;
public AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread, Executor others, Executor paging) {
this.diskIO = diskIO;
this.networkIO = networkIO;
this.mainThread = mainThread;
this.others = others;
this.paging = paging;
}
public AppExecutors() {
this(Executors.newSingleThreadExecutor(), Executors.newFixedThreadPool(3),
new MainThreadExecutor(), Executors.newSingleThreadExecutor(),
Executors.newFixedThreadPool(4));
}
public Executor diskIO() {
return diskIO;
}
public Executor networkIO() {
return networkIO;
}
public Executor mainThread() {
return mainThread;
}
public Executor others() {
return others;
}
public Executor paging() {
return paging;
}
private static class MainThreadExecutor implements Executor {
private Handler mainThreadHandler = new Handler(Looper.getMainLooper());
@Override
public void execute(@NonNull Runnable command) {
mainThreadHandler.post(command);
}
}
}
in your activity / fragment:
yourObjectsViewModel = new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(getActivity().getApplication())).get(YourObjectsViewModel.class);
yourObjectsViewModel.getYourObjectPagedList().observe(getViewLifecycleOwner(), new Observer<PagedList<TopRatedMovie>>() {
@Override
public void onChanged(PagedList<YourObject> results) {
Log.d("log", " onChanged list size: " + results.size());
yourAdapter.submitList(results);
}
});
In your adapter:
public class YourPagedListAdapter extends PagedListAdapter<YourObject,
RecyclerView.ViewHolder> {
If u have any questions feel free to ask.
Upvotes: 1
Reputation: 104
you could add onScrollStateChanged
listener to your RecyclerView to detect the current position of your RecyclerView, then add your logic to fetch in you desired certain condition
Upvotes: 0