2

I am building an Android app that has a user comments page that may contain up to 1,000 comments. It won't be a good practice trying to fetch and load all the comments into the recyclerView all at once. So, I want an approach that will load the comments in batches, maybe 10 at a time, and when the user scrolls the recylcerView, it should load another 10 etc.

The problem is that I am using Volley to get the comments as JSON objects and I don't think Volley has batch processing. I am also not sure if some of the pagination would be done on my PHP file. I have watched several tutorials on Youtube and checked on Stack Overflow for answers including This and This But they don't really address my issue.

This is my PHP code:

<?php

    define('DB_HOST','*******');
    define('DB_USER','********');
    define('DB_PASS','********');
    define('DB_NAME','***********');

    $conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);

    if(mysqli_connect_errno()){
        die('Unable to connect to database '.mysqli_connect_error());
    }

        $conn->set_charset("utf8mb4");

        $storyID = $_GET["storyid"];

    $stmt = $conn->prepare("SELECT comments_table.comment_id, comments_table.username, comments_table.comment, comments_table.date_time, comments_table.story_id, comments_table.imageURL, comments_table.number_of_likes, users.title, comments_table.is_reply, comments_table.reply_username, comments_table.reply_comment, comments_table.reply_id  FROM comments_table INNER JOIN users ON comments_table.username = users.username WHERE comments_table.story_id = '$storyID'");

    $stmt->execute();

    $stmt->bind_result($comment_id, $username, $comment, $date_time, $story_id, $imageURL, $number_of_likes, $title, $is_reply, $reply_username, $reply_comment, $reply_id);

    $comments = array();

    while($stmt->fetch()){
        $temp = array();
        $temp['comment_id'] = $comment_id;
        $temp['username'] = $username;
        $temp['comment'] = $comment;
                $temp['date_time'] = $date_time;
                $temp['story_id'] = $story_id;
                $temp['imageURL'] = $imageURL;
                $temp['number_of_likes'] = $number_of_likes;
                $temp['title'] = $title;
                $temp['is_reply'] = $is_reply;
                $temp['reply_username'] = $reply_username;
                $temp['reply_comment'] = $reply_comment;
                $temp['reply_id'] = $reply_id;

        array_push($comments, $temp);

    }

    echo json_encode($comments);

This is the code snippet I used in retrieving the comments using Volley:

private void loadComments() {
        progressBar.setVisibility(View.VISIBLE);
        StringRequest stringRequest = new StringRequest(Request.Method.GET, URL_COMMENTS + String.valueOf(storyID),
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        try {
                            JSONArray array = new JSONArray(response);
                            for (int i = 0; i < array.length(); i++) {
                                JSONObject comment = array.getJSONObject(i);

                                if (comment.getInt("story_id") == storyID) {
                                    commentList.add(new GetComments(
                                            comment.getInt("comment_id"),
                                            comment.getString("username"),
                                            comment.getString("comment"),
                                            comment.getInt("story_id"),
                                            comment.getString("imageURL"),
                                            comment.getString("date_time"),
                                            comment.getInt("number_of_likes"),
                                            comment.getString("title"),
                                            comment.getInt("is_reply"),
                                            comment.getInt("reply_id"),
                                            comment.getString("reply_username"),
                                            comment.getString("reply_comment")
                                    ));

                                }

                            }

                            if (commentList.isEmpty()){
                                noCommentTextView.setVisibility(View.VISIBLE);
                            }
                            //creating adapter object and setting it to recyclerview
                            adapterJSON = new CommentAdapter(getApplicationContext(), commentList, Comment.this, rootView, storyID);
                            recyclerView.setAdapter(adapterJSON);

                        } catch (JSONException e) {
                            progressBar.setVisibility(View.GONE);
                            Toast.makeText(Comment.this,"Error loading comments: Check internet connection...",Toast.LENGTH_LONG).show();
                            e.printStackTrace();
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        progressBar.setVisibility(View.GONE);
                        Toast.makeText(Comment.this,"Error loading comments: Check internet connection...",Toast.LENGTH_LONG).show();
                    }
                });


        final RequestQueue requestQueue = Volley.newRequestQueue(Comment.this);
        requestQueue.add(stringRequest);
        requestQueue.start();

        requestQueue.addRequestFinishedListener(new RequestQueue.RequestFinishedListener<Object>() {
            @Override
            public void onRequestFinished(Request<Object> request) {
                progressBar.setVisibility(View.GONE);
            }
        });

    }

And these are my recyclerView and layoutManager imlementation:

        recyclerView = findViewById(R.id.recyclerView);
        linearLayoutManager = new LinearLayoutManager(this);
        linearLayoutManager.setReverseLayout(true);
        linearLayoutManager.setStackFromEnd(true);
        recyclerView.setLayoutManager(linearLayoutManager);

Any solution on how to achieve this or offering the right approach will be highly appreciated. Thank you.

oracle
  • 66
  • 1
  • 10

2 Answers2

1

Here is the modified code as suggested by steve Kamau above. This is how I have modified my code:

My modified PHP code:

<?php

    define('DB_HOST','*******');
    define('DB_USER','*******');
    define('DB_PASS','******');
    define('DB_NAME','*********');

    $conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);

    if(mysqli_connect_errno()){
        die('Unable to connect to database '.mysqli_connect_error());
    }

        //mysqli_set_charset($conn,"utf8mb4_unicode_ci");
        $conn->set_charset("utf8mb4");

        $storyID = $_GET["storyid"];
        $limit = $_GET["limit"];
        $offset = $_GET["offset"];

    $stmt = $conn->prepare("SELECT comments_table.comment_id, comments_table.username, comments_table.comment, comments_table.date_time, comments_table.story_id, comments_table.imageURL, comments_table.number_of_likes, users.title, comments_table.is_reply, comments_table.reply_username, comments_table.reply_comment, comments_table.reply_id  FROM comments_table INNER JOIN users ON comments_table.username = users.username WHERE comments_table.story_id = '$storyID' ORDER BY comments_table.date_time DESC LIMIT $limit OFFSET $offset");

    $stmt->execute();

    $stmt->bind_result($comment_id, $username, $comment, $date_time, $story_id, $imageURL, $number_of_likes, $title, $is_reply, $reply_username, $reply_comment, $reply_id);

    $comments = array();

    while($stmt->fetch()){
        $temp = array();
        $temp['comment_id'] = $comment_id;
        $temp['username'] = $username;
        $temp['comment'] = $comment;
                $temp['date_time'] = $date_time;
                $temp['story_id'] = $story_id;
                $temp['imageURL'] = $imageURL;
                $temp['number_of_likes'] = $number_of_likes;
                $temp['title'] = $title;
                $temp['is_reply'] = $is_reply;
                $temp['reply_username'] = $reply_username;
                $temp['reply_comment'] = $reply_comment;
                $temp['reply_id'] = $reply_id;

        array_push($comments, $temp);

    }

    echo json_encode($comments);

I then initialised two Integer values:

int limit = 10;
int offset = 0;

Then I created an OnscrollListener for my recyclerView and created one more method to test if the user has scrolled to the last item:

private RecyclerView.OnScrollListener endOnScrollListener = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            if(isLastItemDisplaying(recyclerView)){
                Log.i("Reached end: ", "Load more");
                loadMoreComments();
            }
        }
    };

    private boolean isLastItemDisplaying(RecyclerView recyclerView){
        //Check if the adapter item count is greater than 0
        if(recyclerView.getAdapter().getItemCount() != 0){
            //get the last visible item on screen using the layout manager
            int lastVisibleItemPosition = ((LinearLayoutManager)recyclerView.getLayoutManager()).findLastCompletelyVisibleItemPosition();

            if(lastVisibleItemPosition != RecyclerView.NO_POSITION && lastVisibleItemPosition == recyclerView.getAdapter().getItemCount()-1){
                return true;
            }

        }
        return false;

    }

And finally, here is my loadMoreComments() method:

private void loadMoreComments() {
        refreshLayout.setRefreshing(true);
        StringRequest stringRequest = new StringRequest(Request.Method.GET, URL_COMMENTS + String.valueOf(storyID)+"&limit="+String.valueOf(limit)
                +"&offset="+String.valueOf(offset),
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        try {
                            JSONArray array = new JSONArray(response);
                            for (int i = 0; i < array.length(); i++) {
                                JSONObject comment = array.getJSONObject(i);

                                if (comment.getInt("story_id") == storyID) {

                                    commentList.add(adapterJSON.getItemCount(), (new GetComments(
                                            comment.getInt("comment_id"),
                                            comment.getString("username"),
                                            comment.getString("comment"),
                                            comment.getInt("story_id"),
                                            comment.getString("imageURL"),
                                            comment.getString("date_time"),
                                            comment.getInt("number_of_likes"),
                                            comment.getString("title"),
                                            comment.getInt("is_reply"),
                                            comment.getInt("reply_id"),
                                            comment.getString("reply_username"),
                                            comment.getString("reply_comment")
                                    )));
                                    adapterJSON.notifyItemInserted(adapterJSON.getItemCount());
                                }


                            }


                        } catch (JSONException e) {
                            progressBar.setVisibility(View.GONE);
                            Toast.makeText(Comment.this,"Error loading comments: Check internet connection...",Toast.LENGTH_LONG).show();
                            e.printStackTrace();
                            Log.i("Error:", e.getMessage());
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        progressBar.setVisibility(View.GONE);
                        Toast.makeText(Comment.this,"Error loading comments: Check internet connection...",Toast.LENGTH_LONG).show();
                        Log.i("Error:", error.getMessage());
                    }
                });


        final RequestQueue requestQueue = Volley.newRequestQueue(Comment.this);
        requestQueue.add(stringRequest);
        requestQueue.start();

        requestQueue.addRequestFinishedListener(new RequestQueue.RequestFinishedListener<Object>() {
            @Override
            public void onRequestFinished(Request<Object> request) {
                refreshLayout.setRefreshing(false);
                offset = offset + 5;
            }
        });

    }
oracle
  • 66
  • 1
  • 10
0

I have worked with pagination before with REST apis. But for your case, the easiest way is doing this:

When you make your first call to get the first 10, append the initial value 10 in your api parameter query like so https://example.com/index.php?limit=10;

In your php code, receive the value 'limit' and append this to your sql query like so :

SELECT * from comments_table WHERE comments_table.story_id = '$storyID' ORDER BY create_date DESC LIMIT 10 OFFSET '$limit'

Check more under LIMIT and OFFSET

This means your android app will keep a counter of sorts for the limit. You can easily achieve this by:

int limit = 10; //initial limit

//after success result from your api, just append a count
limit = limit + 10;
//and use this new limit in subsequent api calls to fetch comments

Most important is ofcourse tracking scrolling events to know if user reached end of the comments list, easily done by this user https://stackoverflow.com/a/46342525/4209724

Steve Kamau
  • 2,755
  • 10
  • 42
  • 73
  • Ok, let me try this and I'll get back. Thank you for your time. – oracle Oct 29 '19 at 14:45
  • Steve, you rock man. It worked like magic. I have battled this for a long time and I can move on to other things. Thank you so much. – oracle Oct 29 '19 at 16:37