I want to implement Endless Scrolling functionality on RecyclerView using RxJava.
What I want: I want to fetch first 10 data from API call and display it in RecyclerView. After user scrolled down these 10 data, I want to make another API call and pull another 10 data. Again when user scrolls down after these latest 10 data, I have to make API call for another 10 data. This will continues until API call gives all the data.
I tried with take operator but not getting desired result. I searched for different operators but not getting single operator or any combination of operator to achieve this. Any help will be appreciated.
My code is like below.
SalesDashboardActivity.java
public class SalesDashboardActivity2 extends BaseActivity implements SalesDashboardView {
List<DashboardStatusBean> inProgressList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sales_dashboard2);
ButterKnife.bind(this);
setSupportActionBar(mToolbar);
mSalesDashboardPresenter.attachView(this);
getDashboardData();
}
private void getDashboardData() {
SearchLoansInput searchLoansInput = new SearchLoansInput();
searchLoansInput.setUserName(sessionManager.getMobileNumber());
if (Util.isInternetConnection(this)) {
mSalesDashboardPresenter.handleSalesInquiryRequest(searchLoansInput);
} else {
displayToastMessage("Internet is not available! Check your internet connection.");
}
}
@Override
public void handleSalesInquiryResponse(SearchLoansOutput searchLoansOutputData) {
if (searchLoansOutputData.isSuccess()) {
Util.SALES_IN_PROGRESS_LIST.clear();
for (int i = 0; i < searchLoansOutputData.getLoanDetailList().size(); i++) {
DashboardStatusBean dashboardStatusBean = new DashboardStatusBean();
SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy");
String approveDate = null;
String createdDate = null;
if (searchLoansOutputData.getLoanDetailList().get(i).getCreatedDate() != null) {
createdDate = String.valueOf(sdf.format(searchLoansOutputData.getLoanDetailList().get(i).getCreatedDate()));
}
if (searchLoansOutputData.getLoanDetailList().get(i).getApprovedDate() != null) {
approveDate = String.valueOf(sdf.format(searchLoansOutputData.getLoanDetailList().get(i).getApprovedDate()));
}
String loanNumber = searchLoansOutputData.getLoanDetailList().get(i).getLoanNumber();
String firstName = searchLoansOutputData.getLoanDetailList().get(i).getFirstName();
String lastName = searchLoansOutputData.getLoanDetailList().get(i).getLastName();
String mobileNumber = searchLoansOutputData.getLoanDetailList().get(i).getMobileNumber();
String status = searchLoansOutputData.getLoanDetailList().get(i).getStatus().getDisplayName();
String loanAmount = String.valueOf(searchLoansOutputData.getLoanDetailList().get(i).getLoanAmount().setScale(0, BigDecimal.ROUND_UP));
String loanAppId = searchLoansOutputData.getLoanDetailList().get(i).getId();
BigDecimal productAmount = searchLoansOutputData.getLoanDetailList().get(i).getTotalProductAmount();
BigDecimal downPayment = searchLoansOutputData.getLoanDetailList().get(i).getDownPayment();
BigDecimal processingFee = searchLoansOutputData.getLoanDetailList().get(i).getProcessingFee();
String tenure = searchLoansOutputData.getLoanDetailList().get(i).getDesiredItemPaybackTenure();
String sourceAcquisition = searchLoansOutputData.getLoanDetailList().get(i).getSourceAcquisition();
List<SingleDocumentOuput> documentUpdatedList = searchLoansOutputData.getLoanDetailList().get(i).getDocOutputList();
dashboardStatusBean.setCreatedDate(createdDate);
dashboardStatusBean.setApproveDate(approveDate);
dashboardStatusBean.setLoanNumber(loanNumber);
dashboardStatusBean.setFirstName(firstName);
dashboardStatusBean.setLastName(lastName);
dashboardStatusBean.setMobileNumber(mobileNumber);
dashboardStatusBean.setStatus(status);
dashboardStatusBean.setLoanAppId(loanAppId);
dashboardStatusBean.setProductAmount(productAmount);
dashboardStatusBean.setDownPayment(downPayment);
dashboardStatusBean.setProcessingFee(processingFee);
dashboardStatusBean.setTenure(tenure);
dashboardStatusBean.setLoanAmount(loanAmount);
dashboardStatusBean.setDocumentUpdatedList(documentUpdatedList);
if (status.equals(LoanApplicationStatus.STILL_FILLING.getDisplayName()) ||
status.equals(LoanApplicationStatus.ONBOARDIN_IN_PROG.getDisplayName()) ||
status.equals(LoanApplicationStatus.INITIATED.getDisplayName()) ||
status.equals(LoanApplicationStatus.ADDITIONAL_DATA_REQ.getDisplayName()) ||
status.equals(LoanApplicationStatus.UPLOAD_IN_PROG.getDisplayName()) ||
status.equals(LoanApplicationStatus.VERIFIED.getDisplayName()) ||
status.equals(LoanApplicationStatus.LEAD.getDisplayName())) {
inProgressList.add(dashboardStatusBean);
}
Util.SALES_IN_PROGRESS_LIST = inProgressList;
}
} else {
displayMessage(searchLoansOutputData.getErrorCode(), searchLoansOutputData.getErrorMessage());
}
}
}
SalesLoginDashboardPresenter.java
public class SalesLoginDashboardPresenter implements Presenter {
private SalesDashboardView mView;
private final SalesLoginInquiryUseCase mSalesInquiryUseCase;
@Inject
public SalesLoginDashboardPresenter(SalesLoginInquiryUseCase salesLoginInquiryUseCase) {
mSalesInquiryUseCase = salesLoginInquiryUseCase;
}
@Override
public void attachView(View v) {
mView = (SalesDashboardView) v;
}
public void handleSalesInquiryRequest(SearchLoansInput searchLoansInput) {
mView.displayLoadingScreen();
mSalesInquiryUseCase.setSalesLoginInquiryBody(searchLoansInput);
mSalesInquiryUseCase.execute()
.subscribe(this::onSalesInquiryReceived, this::onSalesInquiryError);
}
private void onSalesInquiryError(Throwable throwable) {
mView.hideLoadingScreen();
mView.displayErrorMessage("Error fetching data");
throwable.printStackTrace();
}
private void onSalesInquiryReceived(SearchLoansOutput searchLoansOutput) {
mView.hideLoadingScreen();
mView.handleSalesInquiryResponse(searchLoansOutput);
}
}
SalesLoginInquiryUseCase.java
public class SalesLoginInquiryUseCase extends Usecase<SearchLoansOutput> {
private final MerchantRepository mRepository;
private final Scheduler mUiThread;
private final Scheduler mExecutorThread;
private SearchLoansInput mSearchLoansInput;
private String mCookie;
@Inject
public SalesLoginInquiryUseCase(
MerchantRepository repository,
@Named("ui_thread") Scheduler uiThread,
@Named("executor_thread") Scheduler executorThread) {
mRepository = repository;
mUiThread = uiThread;
mExecutorThread = executorThread;
}
public void setSalesLoginInquiryBody(SearchLoansInput searchLoansInput){
mSearchLoansInput = searchLoansInput;
}
@Override
public Observable<SearchLoansOutput> buildObservable() {
return mRepository.postSalesLoginDetailsInquiryBody(mSearchLoansInput)
.observeOn(mUiThread)
.subscribeOn(mExecutorThread);
}
}
I'm doing exactly an exercise about endless scroll on apps and learning RxJava.
I was gandalfing around StackOverflow and other site, in search of a good samples and guides.
So far I found this one, which I also report here (of course, copyright goes to the gist owner).
ApiService.class
public static Observable<List<String>> paginatedThings(final Observable<Void> onNextObservable) {
return Observable.create(new Observable.OnSubscribe<List<String>>() {
@Override
public void call(final Subscriber<? super List<String>> subscriber) {
onNextObservable.subscribe(new Observer<Void>() {
int latestPage = -1;
@Override
public void onCompleted() {
subscriber.onCompleted();
}
@Override
public void onError(Throwable e) {
subscriber.onError(e);
}
@Override
public void onNext(Void aVoid) {
latestPage++;
List<String> pageItems = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
pageItems.add("page " + latestPage + " item " + i);
}
subscriber.onNext(pageItems);
}
});
}
});
}
ReactiveList.java
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.example.rxandroid.api.RepresentativeApi;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import butterknife.ButterKnife;
import butterknife.InjectView;
import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.subscriptions.CompositeSubscription;
import timber.log.Timber;
import static rx.android.app.AppObservable.bindActivity;
/**
* Created by Dustin on 2/14/15.
*/
public class ReactiveList extends Activity {
@InjectView(R.id.reactiveList)
RecyclerView reactiveList;
private ReactiveRecyclerAdapter adapter = new ReactiveRecyclerAdapter();
private LinearLayoutManager layoutManager;
private CompositeSubscription subscriptions = new CompositeSubscription();
private List<String> mockItems = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.reactive_list);
ButterKnife.inject(this);
for (int i = 0; i < 100; i++) {
mockItems.add(String.valueOf(i));
}
reactiveList.setHasFixedSize(true);
reactiveList.setAdapter(adapter);
layoutManager = new LinearLayoutManager(this);
reactiveList.setLayoutManager(layoutManager);
adapter.addAll(mockItems);
Observable<Void> pageDetector = Observable.create(new Observable.OnSubscribe<Void>() {
@Override
public void call(final Subscriber<? super Void> subscriber) {
reactiveList.setOnScrollListener(new RecyclerView.OnScrollListener() {
int pastVisibleItems, visibleItemCount, totalItemCount;
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
visibleItemCount = layoutManager.getChildCount();
totalItemCount = layoutManager.getItemCount();
pastVisibleItems = layoutManager.findFirstVisibleItemPosition();
if ((visibleItemCount+pastVisibleItems) >= totalItemCount) {
subscriber.onNext(null);
}
}
});
}
}).debounce(400, TimeUnit.MILLISECONDS);
bindActivity(this, pageDetector);
Observable<List<String>> listItemObservable = RepresentativeApi.paginatedThings(pageDetector);
bindActivity(this, listItemObservable);
subscriptions.add(listItemObservable.observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<List<String>>() {
@Override
public void onCompleted() {
Timber.d("completed");
}
@Override
public void onError(Throwable e) {
Timber.e("error: " + e.getMessage());
}
@Override
public void onNext(List<String> strings) {
adapter.addAll(strings);
}
}));
}
public static class ReactiveRecyclerAdapter extends RecyclerView.Adapter<ReactiveRecyclerAdapter.ReactiveViewHolder> {
private List<String> items = new ArrayList<>();
public void addAll(List<String> moreItems) {
items.addAll(moreItems);
notifyDataSetChanged();
}
@Override
public ReactiveViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1,parent,false);
ReactiveViewHolder vh = new ReactiveViewHolder(v);
return vh;
}
@Override
public void onBindViewHolder(ReactiveViewHolder holder, int position) {
String item = items.get(position);
holder.label.setText(item);
}
@Override
public int getItemCount() {
return items.size();
}
public static class ReactiveViewHolder extends RecyclerView.ViewHolder {
TextView label;
public ReactiveViewHolder(View itemView) {
super(itemView);
label = (TextView) itemView.findViewById(android.R.id.text1);
}
}
}
}
And if you are interested on other tools, give a look at Google Architecture.
The goal is to simplify app code (and why not, coders life): you will find the Paging library which you can use to solve endless scroll (I'm currently studying this too). By the way, it uses RxJava2.
If you want, there is an example on this GitHub repo and it is done with Kotlin(!).
Conclusion: endless scroll can be very tricky and I strongly advice to take your time to learn and understand how it works, be it with libraries or not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With