content: package com.alexvasilkov.gestures.sample.demo.adapter;
  
  import android.view.ViewGroup;
  import android.widget.TextView;
  
  import androidx.annotation.NonNull;
  import androidx.recyclerview.widget.GridLayoutManager;
  import androidx.recyclerview.widget.RecyclerView;
  
  import com.alexvasilkov.android.commons.ui.Views;
  import com.alexvasilkov.gestures.sample.R;
  
  import java.util.List;
  
  abstract class DefaultEndlessRecyclerAdapter<VH extends RecyclerView.ViewHolder>
          extends EndlessRecyclerAdapter<RecyclerView.ViewHolder> {
  
      private static final int EXTRA_LOADING_TYPE = Integer.MAX_VALUE;
      private static final int EXTRA_ERROR_TYPE = Integer.MAX_VALUE - 1;
  
      private final GridLayoutManager.SpanSizeLookup spanSizes =
              new GridLayoutManager.SpanSizeLookup() {
                  @Override
                  public int getSpanSize(int pos) {
                      return pos == getCount() && (isLoading() || isError()) ? spanCount
                              : originalSpanLookup == null ? 1 : originalSpanLookup.getSpanSize(pos);
                  }
              };
  
      private GridLayoutManager.SpanSizeLookup originalSpanLookup;
      private int spanCount;
  
      private boolean oldIsLoading;
      private boolean oldIsError;
  
  
      @NonNull
      @Override
      public final RecyclerView.ViewHolder onCreateViewHolder(
              @NonNull ViewGroup parent,
              int viewType
      ) {
          if (viewType == EXTRA_LOADING_TYPE) {
              return new LoadingViewHolder(parent);
          } else if (viewType == EXTRA_ERROR_TYPE) {
              return new ErrorViewHolder(parent, this);
          } else {
              return onCreateHolder(parent, viewType);
          }
      }
  
      protected abstract VH onCreateHolder(ViewGroup parent, int viewType);
  
      @Override
      public final void onBindViewHolder(
              @NonNull RecyclerView.ViewHolder holder,
              int position,
              @NonNull List<Object> payloads
      ) {
          onBindViewHolder(holder, position);
      }
  
      @SuppressWarnings("unchecked")
      @Override
      public final void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
          if (holder instanceof LoadingViewHolder) {
              onBindLoadingView(((LoadingViewHolder) holder).loading);
          } else if (holder instanceof ErrorViewHolder) {
              onBindErrorView(((ErrorViewHolder) holder).error);
          } else {
              onBindHolder((VH) holder, position);
          }
      }
  
      protected abstract void onBindLoadingView(TextView loadingText);
  
      protected abstract void onBindErrorView(TextView errorText);
  
      protected abstract void onBindHolder(VH holder, int position);
  
      @Override
      public final int getItemViewType(int position) {
          if (position == getCount()) {
              if (isLoading()) {
                  return EXTRA_LOADING_TYPE;
              } else if (isError()) {
                  return EXTRA_ERROR_TYPE;
              }
          }
  
          int type = getViewType(position);
  
          if (type == EXTRA_LOADING_TYPE) {
              throw new IllegalArgumentException(
                      "Cannot use " + EXTRA_LOADING_TYPE + " as view type");
          }
          if (type == EXTRA_ERROR_TYPE) {
              throw new IllegalArgumentException("Cannot use " + EXTRA_ERROR_TYPE + " as view type");
          }
  
          return type;
      }
  
      @SuppressWarnings({ "UnusedParameters", "WeakerAccess", "SameReturnValue" })
      // Public API (may be reused)
      protected int getViewType(int position) {
          return 0;
      }
  
  
      @Override
      protected void onLoadingStateChanged() {
          super.onLoadingStateChanged();
  
          if (oldIsLoading) {
              if (isError()) {
                  notifyItemChanged(getCount()); // Switching to error view
              } else if (!isLoading()) {
                  notifyItemRemoved(getCount()); // Loading view is removed
              }
          } else if (oldIsError) {
              if (isLoading()) {
                  notifyItemChanged(getCount()); // Switching to loading view
              } else if (!isError()) {
                  notifyItemRemoved(getCount()); // Error view is removed
              }
          } else {
              if (isLoading() || isError()) {
                  notifyItemInserted(getCount()); // Showing loading or error view
              }
          }
  
          oldIsError = isError();
          oldIsLoading = isLoading();
      }
  
      @Override
      public final int getItemCount() {
          return getCount() + (isLoading() || isError() ? 1 : 0);
      }
  
      public abstract int getCount();
  
  
      @Override
      public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
          super.onAttachedToRecyclerView(recyclerView);
          if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
              GridLayoutManager gridManager = (GridLayoutManager) recyclerView.getLayoutManager();
              spanCount = gridManager.getSpanCount();
              originalSpanLookup = gridManager.getSpanSizeLookup();
              gridManager.setSpanSizeLookup(spanSizes);
          }
      }
  
      @Override
      public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
          super.onDetachedFromRecyclerView(recyclerView);
          if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
              GridLayoutManager gridManager = (GridLayoutManager) recyclerView.getLayoutManager();
              gridManager.setSpanSizeLookup(originalSpanLookup);
              originalSpanLookup = null;
              spanCount = 1;
          }
      }
  
  
      private static class LoadingViewHolder extends RecyclerView.ViewHolder {
          final TextView loading;
  
          LoadingViewHolder(ViewGroup parent) {
              super(Views.inflate(parent, R.layout.demo_item_extra_loading));
              loading = itemView.findViewById(R.id.extra_loading_text);
          }
      }
  
      private static class ErrorViewHolder extends RecyclerView.ViewHolder {
          final TextView error;
  
          ErrorViewHolder(ViewGroup parent, final EndlessRecyclerAdapter<?> adapter) {
              super(Views.inflate(parent, R.layout.demo_item_extra_error));
              error = itemView.findViewById(R.id.extra_error);
              error.setOnClickListener(view -> adapter.reloadNextItemsIfError());
          }
      }
  
  }
  