0

I have an internal-database(SQLite) with many entries. So I decided to load the first 20 entries in the listview when the user starts the activity and when he scrolls down he can load 20 more each time pressing a button if there are entries left.

EDIT

//onCreate()
acceptedLogs = helper.getLogsRange(0, LOAD_AMOUNT);
        loadedEntriesCounter = LOAD_AMOUNT;

        logAdapter = new LogAdapter(acceptedLogs);
        logRecyclerView.setAdapter(logAdapter);
        logRecyclerView.setHasFixedSize(false);
        linearLayoutManager = new LinearLayoutManager(getContext());
        logRecyclerView.setLayoutManager(linearLayoutManager);




@Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                visibleItemCount = logRecyclerView.getChildCount();
                totalItemCount = logRecyclerView.getLayoutManager().getItemCount();
                int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
                if (loading) { // boolean set to true if you are already loading and false after you will update adaptor
                    if (totalItemCount > previousTotalCount) {
                        previousTotalCount = totalItemCount;
                    }
                }
                if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItemPosition + VISIBLE_THRESHOLD)) {
                    loading = true;
                    List<Log> newLogs = helper.getLogsRange(loadedEntriesCounter, LOAD_AMOUNT);
                    loadedEntriesCounter += LOAD_AMOUNT;
                    logAdapter.logs.addAll(newLogs);
                    logAdapter.notifyDataSetChanged();

                }
            }

The loading only happens ones! Where do I have to set loading on false?

1 Answers1

1

First, better use RecyclerView with viewholder, second you better load data on scroll listener and some trick, for example you have loaded 20 items then when you scroll list there will be good to load data when you scroll list and you scrolled to for example to 18 item at bottom, here you start your async task and when you scroll to 20 your loading will finish and you will update list with 20 more so user will not even see when you are loading.

mVisibleThreshold = 2 // this is count items to bottom of current list when load will start.

here is my on scroll listener for recyclerview (can be adjusted to your listview):

mProductsResultsList.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {


                super.onScrolled(recyclerView, dx, dy);
                mVisibleItemCount = mProductsResultsList.getChildCount();
                mTotalItemCount = mProductsResultsLayoutManager.getItemCount();
                mFirstVisibleItem = mProductsResultsLayoutManager.findFirstVisibleItemPosition();

                if (mLoadingInProgress) { // boolean set to true if you are already loading and false after you will update adaptor
                    if (mTotalItemCount > mPreviousTotal) {

                        mPreviousTotal = mTotalItemCount;
                    }
                }
                if (!mLoadingInProgress && (mTotalItemCount - mVisibleItemCount)
                        <= (mFirstVisibleItem + mVisibleThreshold)) {
                    mLoadingInProgress = true;
                        mLastPageRequest++; // current page to load and add
                        // Here you load data and update adapter
                        // if in async task then start it here and set mLoadingInProgress to true and onPostExecute add to list result and make notifyDatasetChanged on adapter then mLoadingInProgress to false.


                }
            }
        });

One more: don't touch any views in background tasks (otherwise you will stuck main thread), make all updates and notifications after task code in onPostExecute with RunOnUIThread.

okay as you are interested in this solution will improve answer:

mLastPageRequest - int I use to know which page I was loaded and which i must load next it's incrementing each time by 1 with ++

after loading data (from database or from web request you should add downloaded items to your list). Currently in my project my list is "mCategoryProducts" and this is my adapter mProductsResultsListAdapter.

all U need is to add all next items you downloaded to list you attached to adapter with

mCategoryProducts.addAll(downloadedListProducts); // here you are adding items you just loaded to existing list to the end, 

now next code:

public void displayGotResults(){
        if(mCategoryProducts != null){
            if(mProductsResultsListAdapter == null && mLastPageRequest==1){
                mProductsResultsListAdapter = new ProductListRecyclerViewAdapter(this, mCategoryProducts, true);
                mProductsResultsList.setAdapter(mProductsResultsListAdapter);
                mProductsResultsListAdapter.setClickListener(this);
            }else{
                mProductsResultsListAdapter.notifyDataSetChanged();
                //notifyItemInserted(mSearchList.size()-1);
            }
            if(mCategoryProducts.size()>0) {
                mCategoryIsEmptyInfo.setVisibility(View.GONE);
            }else{
                mCategoryIsEmptyInfo.setVisibility(View.VISIBLE);
            }
        }else{
            mCategoryIsEmptyInfo.setVisibility(View.VISIBLE);
        }
    }

Here if adapter not already initialised then we create it and attaching to it our list mCategoryProducts otherwise if this is second loading then we simple notify adapter that "Hey man new data is comming :)" with:

mProductsResultsListAdapter.notifyDataSetChanged();

Notes: mCategoryIsEmptyInfo - is view which i show when there is no items to display.

Here you are my custom adaptor with interface for clicks that can be handled in activity )

public class ProductListRecyclerViewAdapter extends RecyclerView.Adapter<ProductListRecyclerViewAdapter.ProductListRecyclerViewHolder> {
    private LayoutInflater mInflater;
    private Context mContext;
    private DrawerLayout mNavigationDrawer;
    private ClickListener mClickListener;
    private LongClickListener mLongClickListener;
    List<ProductModel> navigationData = Collections.emptyList();
    private ImageLoader mImageLoader;
    private VolleyServiceSingleton mVollayService;
    private boolean mShowThumbNail;

    //For animations
    private int mLastPositiion = -1;

    public ProductListRecyclerViewAdapter (Context context, List<ProductModel> navData, boolean showThumbNail){
        mInflater = LayoutInflater.from(context);
        this.navigationData = navData;
        mContext = context;
        mVollayService = VolleyServiceSingleton.getInstance();
        mImageLoader = mVollayService.getImageLoader();
        mShowThumbNail = showThumbNail;
    }


    @Override
    public ProductListRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.list_item_product, parent, false);
        ProductListRecyclerViewHolder holder = new ProductListRecyclerViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(final ProductListRecyclerViewHolder viewHolder, int position) {
        ProductModel currentItem = navigationData.get(position);
        viewHolder.productBrand.setText(currentItem.ManufacturerName);
        viewHolder.productName.setText(currentItem.Name);
        viewHolder.productCode.setText(currentItem.Code);
        viewHolder.productPrice.setText(String.format(mContext.getString(R.string.money_sign), Utils.decimalWithCommas(String.valueOf(currentItem.DealerPrice))));

        if(Constants.SHOW_IMAGE_THUMBNAILS_IN_LIST && mShowThumbNail){
            if(currentItem.CatalogImage != null && currentItem.CatalogImage.contains("http")){
                viewHolder.productThumbnail.setVisibility(View.VISIBLE);
                mImageLoader.get(currentItem.CatalogImage, new ImageLoader.ImageListener() {
                    @Override
                    public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
                        viewHolder.productThumbnail.setImageBitmap(response.getBitmap());
                    }

                    @Override
                    public void onErrorResponse(VolleyError error) {

                    }
                });
            }
        }else{
            viewHolder.productThumbnail.setVisibility(View.GONE);
        }

    }

    public void setAnimation(View viewToAnimate, int position)
    {
        // If the bound view wasn't previously displayed on screen, it's animated
        if (position > mLastPositiion)
        {
            Animation animation = AnimationUtils.loadAnimation(mContext, android.R.anim.slide_in_left);
            viewToAnimate.startAnimation(animation);
            mLastPositiion = position;
        }
    }
    @Override
    public int getItemCount() {
        return navigationData.size();
    }

    public void setClickListener(ClickListener clickListener){
        this.mClickListener = clickListener;
    }
    public void setLongClickListener(LongClickListener lognClickListener){
        this.mLongClickListener = lognClickListener;
    }


    public class ProductListRecyclerViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener{
        TextView productBrand;
        TextView productName;
        TextView productCode;
        TextView productPrice;
        ImageView productThumbnail;
        public ProductListRecyclerViewHolder(View itemView) {
            super(itemView);
            productBrand = (TextView) itemView.findViewById(R.id.product_brand);
            productName = (TextView) itemView.findViewById(R.id.product_name);
            productCode = (TextView) itemView.findViewById(R.id.product_code);
            productPrice = (TextView) itemView.findViewById(R.id.product_price);
            productThumbnail = (ImageView) itemView.findViewById(R.id.product_thumbnail);
            itemView.setOnClickListener(this);
            itemView.setTag(this);
            itemView.setOnLongClickListener(this);

        }

        @Override
        public void onClick(View v) {
            if(mClickListener!=null){
                mClickListener.itemClicked(v, getAdapterPosition());
            }
        }

        @Override
        public boolean onLongClick(View v) {
            if(mLongClickListener!=null){
                mLongClickListener.itemLongClicked(v, getAdapterPosition());
            }
            return false;
        }
    }
    public interface ClickListener{
        void itemClicked(View view, int position);
    }

    public interface LongClickListener{
        void itemLongClicked(View view, int position);
    }
}

it will probably not work without you update to your needs, but nice sample how must be )

Make attention on

mProductsResultsListAdapter.setClickListener(this);

then in activity you can catch clicks with :

public class ProductListActivity extends ActionBarActivity implements ProductListRecyclerViewAdapter.ClickListener {

    //.....

    @Override
    public void itemClicked(View view, int position) {

    }

    //.....
}

One more thing if you need to catch click for example on specific view in item in list for example on image in view holder then set click listener to "this" and set tag to this view and in activity then you can get tag from view and you will know where you clicked :)

your loading is from database:

 loading = true;
                    List<Log> newLogs = helper.getLogsRange(loadedEntriesCounter, LOAD_AMOUNT);
                    loadedEntriesCounter += LOAD_AMOUNT;
                    logAdapter.logs.addAll(newLogs);
 loading = false; // somewhere here 

but this is not finally correct way to load data you can stuck main thread coz if database will be too big or you will make "heavy" selection you can get stuck for several milisec. you must do all data loading in async tasks and then on task done notify adapter.

in my project i load data from webapi, not from database but in anyway must be done in same way via async task, this is proper way )

class LoadNextPageTask extends AsyncTask<Integer, Void, List<ProductModel>> {
        int pageToLoad;

        public LoadNextPageTask(int pageToLoad){
            this.pageToLoad = pageToLoad;
        }


        @Override
        protected List<ProductModel> doInBackground(Integer... params) {
            return helper.getLogsRange(loadedEntriesCounter, LOAD_AMOUNT); // here apply page to load??? not sure
        }

        @Override
        protected void onPreExecute() {
            mMainLoadingProgress.setVisibility(View.VISIBLE);
            loading = true;
        }

        @Override
        protected void onPostExecute(List<ProductModel> newLogs) {
            loading = false;
            loadedEntriesCounter += LOAD_AMOUNT;
            logAdapter.logs.addAll(newLogs);

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    logAdapter.notifyDataSetChanged();
                }
            });
            mMainLoadingProgress.setVisibility(View.GONE);

        }
    }

above is async task please update to your needs then where data loading simple start it with:

new LoadNextPageTask(pagenumber).execute(); // page is int
Stepan Maksymov
  • 2,580
  • 14
  • 30