Skip to content
Davide Steduto edited this page Dec 22, 2017 · 21 revisions

In this page


Introduction

EndlessScroll / On Load More feature is achieved by an innovative mechanism with no ScrollListener implementation! The call is done in the onBindViewHolder() of the Adapter, by checking the current position if matches with the last position minus the threshold*.
This elaboration is irrelevant compared to the usual callback for the ScrollListener implementation, especially if the progress item is not set.

*️⃣ = Set the threshold to anticipate the loading by calling setEndlessScrollThreshold().

Two scenarios are foreseen for this feature:

  • Automatic loading, where the trigger to load more items, is done automatically :-)
  • Load more upon a user request, where the developer gives the possibility to the user to choose to load more items or not, by simply clicking an inner view in the item.

When there are no more items to load, the progressItem will be notified about the change: notifyItemChanged() will be invoked before the removal. Optionally, the item can be removed after a custom delay to give time to read a possible message. In bindViewHolder() of the item, you should be able to display the messages based on the result status.

There are two new criteria to establish automatically when to disable the endless feature and display a message in the progress item:

  • EndlessTargetCount is the custom limit when the overall loaded items count met the target count set by the user.
  • EndlessPageSize is the final limit to stop further calls to the server when the last items count didn't reach the page size, which means all items are fully loaded.

ℹ️ Note: ProgressItem will be handled as Scrollable Footer Item, but always displayed between main items and others footers.

Automatic load more

This feature is achieved by calling setEndlessScrollListener() method. You will have to provide a Listener and the progress item.

1. Setup

Setup with EndlessScrollListener and with optional criteria to automatically stop loading more items.

mAdapter.setEndlessScrollListener(listener: activity, progressItem: mProgressItem)
        .setEndlessScrollThreshold(numItems: 1) // Default=1
        // (Optional) Automatically disable Endless feature:
        .setEndlessPageSize(numItems: 7)        // if newItems < 7
        .setEndlessTargetCount(numItems: 27)    // if totalItems >= 27
        // New items inserted at the top
        .setTopEndless(true);                   // default is false (bottom)

2. Create a progress item layout

The layout displays a custom progress bar and a TextView* for the custom messages.
Check progress_item.xml layout to have something similar to:
image

3. Create the pojo ProgressItem with its ViewHolder.

The mapping might take in consideration the messages for statuses to handle special cases (see point 5): loading (progress indicator), noMoreLoad*, endlessDisabled*, onError*, onCancel*.

ProgressItem

Check ProgressItem.java sample for more details. Here the main logic:

public enum StatusEnum {
    MORE_TO_LOAD,    // Default = should have an empty Payload
    DISABLE_ENDLESS, // Endless is disabled because user has set limits
    NO_MORE_LOAD,    // Non-Empty payload = Payload.NO_MORE_LOAD
    ON_CANCEL,
    ON_ERROR
}

@Override
public void bindViewHolder(FlexibleAdapter adapter, ProgressViewHolder holder,
                           int position, List payloads) {

	Context context = holder.itemView.getContext();
    holder.progressBar.setVisibility(View.GONE);
    holder.progressMessage.setVisibility(View.VISIBLE);

    if (!adapter.isEndlessScrollEnabled()) {
        setStatus(StatusEnum.DISABLE_ENDLESS);
    } else if (payloads.contains(Payload.NO_MORE_LOAD)) {
        setStatus(StatusEnum.NO_MORE_LOAD);
    }

    switch (this.status) {
    case NO_MORE_LOAD:
        holder.progressMessage.setText(
                context.getString(R.string.no_more_load_retry));
        // Reset to default status for next binding
        setStatus(StatusEnum.MORE_TO_LOAD);
        break;
    case DISABLE_ENDLESS:
        holder.progressMessage.setText(context.getString(R.string.endless_disabled));
        break;
    case ON_CANCEL:
        holder.progressMessage.setText(context.getString(R.string.endless_cancel));
        // Reset to default status for next binding
        setStatus(StatusEnum.MORE_TO_LOAD);
        break;
    case ON_ERROR:
        holder.progressMessage.setText(context.getString(R.string.endless_error));
        // Reset to default status for next binding
        setStatus(StatusEnum.MORE_TO_LOAD);
        break;
    default:
        holder.progressBar.setVisibility(View.VISIBLE);
        holder.progressMessage.setVisibility(View.GONE);
        break;
    }
}

4. Implement the EndlessScrollListener

The onLoadMore() callback will be invoked to load more items from the (local/remote) repository.

/**
 * Loads more data.
 * Use lastPosition and currentPage to know what to load next.
 * lastPosition is the count of the main items without Scrollable Headers.
 */
@Override
public void onLoadMore(int lastPosition, int currentPage) {
    // Preliminary check, left open to implement for the developer:
    // - Normally, we don't want load more items when searching into the current
    //   Collection!
    // - Alternatively, for a special filter, if we want load more items when
    //   filter is active, the new items that arrive from remote, should be
    //   already filtered, according to the searchText, before adding them to
    //   the Adapter!
    if (mAdapter.hasSearchText()) {
        mAdapter.onLoadMoreComplete(null);
        return;
    }

    // Asynchronous call to Local or REST Api to load more items
    callToRepositoryApi();
}

/**
 * New callback from EndlessScrollListener when no more data to load.
 * This method is called if any limit is reached (targetCount or pageSize
 * must be set) AND if new data is temporary unavailable (ex. no connection or
 * no new updates remotely). If no new data, a FlexibleAdapter#notifyItemChanged
 * with a payload Payload#NO_MORE_LOAD is triggered on the progressItem.
 */
@Override
public void noMoreLoad(int newItemsSize) {
    Log.d(TAG, "newItemsSize=" + newItemsSize);
    Log.d(TAG, "Total pages loaded=" + mAdapter.getEndlessCurrentPage());
    Log.d(TAG, "Total items loaded=" + mAdapter.getMainItemCount());
}

/**
 * Display new items and notify user.
 * This, instead, should be a Callback from the previous Asynchronous call.
 */
public void onLoadMoreComplete(final List<AbstractFlexibleItem> newItems) {
    // Callback the Adapter to notify the change:
    // - New items will be added to the end of the main list and progressItem
    //   will be hidden.
    // - Instead, when list is null or empty, and limits are reached, Endless
    //   scroll will be disabled. To enable again, you must call
    //   setEndlessProgressItem(@Nullable T progressItem).
    mAdapter.onLoadMoreComplete(newItems, 5000L)*;

    // You can retrieve the new page number after adding new items!
    Log.d(TAG, "EndlessCurrentPage=" + mAdapter.getEndlessCurrentPage());
    Log.d(TAG, "EndlessPageSize=" + mAdapter.getEndlessPageSize());
    Log.d(TAG, "EndlessTargetCount=" + mAdapter.getEndlessTargetCount());

    // Notify user
    ...
}

*️⃣ = When list is null or empty, and limits are reached, endless scroll will be disabled. To enable again, you must set again the progress item by calling setEndlessProgressItem(@Nullable T progressItem).

5. Special cases

You will be handling the following cases:

  • onSuccess with items: call onLoadMoreComplete(newItems)
    • ProgressItem is removed immediately.
    • Items are added to the end of the list.
  • onSuccess no items: call onLoadMoreComplete(empty list or null, delay*)
    • Internal call and callback to noMoreLoad().
    • ProgressItem may display the updated message for a certain delay.
  • onCancel*: call onLoadMoreComplete(empty list or null, delay*)
    • Push the ON_CANCEL status to the ProgressItem.
    • Internal call and callback to noMoreLoad().
    • ProgressItem may display the cancelled message for a certain delay.
  • onError*: call onLoadMoreComplete(empty list or null, delay*)
    • Push the ON_ERROR status to the ProgressItem.
    • Internal call and callback to noMoreLoad().
    • ProgressItem may display the error message for a certain delay.

*️⃣ = Optional delay with the following behaviours:

  • Providing any delay, ProgressItem is kept to display the message, then automatically removed after the delay.
  • Not providing any delay or 0 delay, ProgressItem is removed immediately and when the user scrolls again, the item will be displayed again, a new call to onLoadMore() is being triggered.

Load more upon a user request

This feature is achieved by calling setEndlessProgressItem() method that distinguishes the manual loading from the automatic loading by the fact that no listener is involved!

1. Setup

Setup with optional criteria to automatically stop loading more items.

mAdapter.setEndlessProgressItem(progressItem: mProgressItem)
        .setEndlessScrollThreshold(numItems: 1) //Default=1
        // Automatically disable Endless feature:
        .setEndlessPageSize(numItems: 7)        //if newItems < 7
        .setEndlessTargetCount(numItems: 27);   //if totalItems >= 27
        // New items inserted at the top
        .setTopEndless(true);                   //default is false (bottom)

2. Create a progress item layout

The layout should support a Button to start the loading with onClick event; A custom progress bar that becomes visible when user taps the button, the button disappears; A TextView for the custom messages.

3. Don't implement the EndlessScrollListener

There's no listener involved... so the method onLoadMore() won't be called.
Basically, you will have to handle click event of the button to load more data when user taps on it.

4. Complete the loading

When the remote call receives the response/items, you will invoke onLoadMoreComplete(newItems) as you normally would do for an automatic loading.

Load more per section

You will have to handle all the steps for manual loading PLUS the addition and the removal of the progress item by yourself as last element of the section.

Note: This cannot be reached by the library since it is a custom event inside your custom item.

Clone this wiki locally