简体   繁体   中英

Android Stop recycler-View adapter binding data for already shown items

I have an app that uses a recyclerView to show results from Google Books API.

In every onBindViewHolder , I ask the client to give me data which leads me eventually to exceeding the rate limit since every scroll calls data.

Let say I got data for position 1-5 and I scroll down to position 6-10 and then go back to 1-5. How can I make sure that it won't call the client again for positions 1-5 since it has already loaded them?

I just want whatever it already called to stay there.

My adapter looks like this ( I deleted some parts like more views so it wont confuse):

public class MyBooksAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<DiscoverBooks> discoverBooks;
    private FirebaseFirestore db;
    private FirebaseAuth auth;
    private String UID, BookID2;
    private int count, gbsSize, counter;

    interface OnDiscoverBookClickListener {
        void onClick(DiscoverBooks discoverBooks);
    }

    private OnDiscoverBookClickListener listener;

    public MyBooksAdapter(int counter, List<DiscoverBooks> discoverBooks, int gbsSize, OnDiscoverBookClickListener listener) {
        this.counter = counter;
        this.discoverBooks = discoverBooks;
        this.listener = listener;
        this.gbsSize = gbsSize;
    }

    @Override
    public int getItemViewType(int position) {
        return AppConstants.IS_NOT_ADS_POSITION;
    }


    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
        View view;
        view = LayoutInflater.from( viewGroup.getContext() ).inflate( R.layout.item_book_mybook, viewGroup, false );
        return new DiscoverBooksViewHolder( view );
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        holder.setIsRecyclable( false );
        if (holder instanceof DiscoverBooksViewHolder) {
            ((MyBooksAdapter.DiscoverBooksViewHolder) holder).bind( (discoverBooks.get( position )), holder );
        }
    }

    @Override
    public int getItemCount() {
        return discoverBooks.size();
    }

    class DiscoverBooksViewHolder extends RecyclerView.ViewHolder {

        private Button tv_Link;
        private CardView cardView;
        private ImageView Iv_BookCover;
        private TextView tv_Author, tv_BorrowedTo, tv_BorrowedTill, tv_DateAdded, tv_Title;
        private ImageButton ib_Options;
        private ToggleButton tb_Status;
        private DiscoverBooks discoverBooks;

        private DiscoverBooksViewHolder(View itemView) {
            super( itemView );

            cardView = itemView.findViewById( R.id.imagecard );
            Iv_BookCover = itemView.findViewById( R.id.iv_BookCover );
            tv_Title = itemView.findViewById( R.id.tv_Title );
            tv_Author = itemView.findViewById( R.id.tv_Author );
            tv_DateAdded = itemView.findViewById( R.id.tv_DateAdded );
            tv_BorrowedTo = itemView.findViewById( R.id.tv_BorrowedTo );
            tv_BorrowedTill = itemView.findViewById( R.id.tv_BorrowedTill );
            tv_Link = itemView.findViewById( R.id.tv_Link );
            ib_Options = itemView.findViewById( R.id.ib_Options );
            tb_Status = itemView.findViewById( R.id.tb_Status );

        }

        private void bind(DiscoverBooks discoverBooks, RecyclerView.ViewHolder viewHolder) {
            this.discoverBooks = discoverBooks;

            db = FirebaseFirestore.getInstance();
            auth = FirebaseAuth.getInstance();

            String BookID = discoverBooks.getBookID();

            if (BookID.length() > AppConstants.UPLOADED_BOOK_LENGTH) {
                db.collection( "Books" ).document( BookID ).get()
                        .addOnCompleteListener( task -> {
                            if (task.isSuccessful()) {
                                DocumentSnapshot document = task.getResult();
                                if (document.exists()) {
                                    DO SOMETHING
                                }
                            }
                        } );

            } else {
            //HERE I CALL GOOGLE BOOKS API
                MyBookClient.getInstance().getBooks( BookID, new JsonHttpResponseHandler() {
                    @Override
                    public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
                        if (response != null) {
                            final MyBook books = MyBook.fromJson( response );
                            //DO SOMETHING
                        }
                    }
                } );
            }

        }

    }

}

Ok, I understood your problem. For every bookID, you fetch its data from Google Books, and now you want to not hit the api for already loaded items.

Simply, create a global variable for ArrayList as

private ArrayList<MyBook> mybooks = new ArrayList();

Call your onBind() in onBindViewHolder() as

((MyBooksAdapter.DiscoverBooksViewHolder) holder).bind(position, discoverBooks.get( position), holder);

Replace your onBind() with this:

private void bind(int position, UserData discoverBooks, RecyclerView.ViewHolder viewHolder) {
    this.discoverBooks = discoverBooks;

    db = FirebaseFirestore.getInstance();
    auth = FirebaseAuth.getInstance();

    String BookID = discoverBooks.getBookID();

    if (BookID.length() > AppConstants.UPLOADED_BOOK_LENGTH) {
        db.collection("Books").document(BookID).get()
                .addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                        DocumentSnapshot document = task.getResult();
                        if (document.exists()) {
                            DO SOMETHING
                        }
                    }
                });

    } else {
        if (position >= myBooks.size() || myBooks.get(position) == null) {
            //If the value doesn't exist in the ArrayList, 
            //it will hit the Google Books API
            MyBookClient.getInstance().getBooks(BookID, new JsonHttpResponseHandler() {
                @Override
                public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
                    if (response != null) {
                        final MyBook books = MyBook.fromJson(response);
                        myBooks.add(position, books);
                        //DO SOMETHING
                    }
                }
            });
        } else {
            //If the value has already been loaded in the list, 
            //directly use it without hitting the API
            final MyBook books = myBooks.get(position);
            //DO SOMETHING
        }
    }
}

This will solve your issue, it will store the already accessed books from the API and will not hit the API again and will use the List to access them.

Apart from this, I'll suggest to go with ViewBinding which will replace your whole DiscoverBooksViewHolder into a single line(in Kotlin) or 2-3 lines(in Java), with viewBinding, you'll not have to declare and initialize views and the views can be accessed directly using Binding , you can then extract your onBind() out of the ViewHolder .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM