Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView Endless Scrolling with RxJava

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);
    }
}
like image 716
Maulik Dodia Avatar asked May 15 '18 14:05

Maulik Dodia


1 Answers

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.

like image 164
JJ86 Avatar answered Oct 17 '22 00:10

JJ86