Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

recyclerView.addOnScrollListener - "retrofit pagination with MVVM" is loading the same response/list

I am using blogger API, retrofit, and MVVM in my app, I trying to use pagination to load more posts when user is scrolling, the problem happening here the response is loading it self "the same list / same ten posts is loading again"

here's my code

PostsClient Class

public class PostsClient {

    private static final String TAG = "PostsClient";

    private static final String KEY = "XYZ sensitive key!";
    private static final String BASE_URL = "https://www.googleapis.com/blogger/v3/blogs/4294497614198718393/";

    private PostInterface postInterface;
    private static PostsClient INSTANCE;

    public PostsClient() {

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        postInterface = retrofit.create(PostInterface.class);

    }

    public static PostsClient getINSTANCE() {
        if(INSTANCE == null){
            INSTANCE = new PostsClient();
        }
        return INSTANCE;
    }



    public Call<PostList> getPostList(){

        return postInterface.getPostList(KEY);
    }



}

[PostViewModel]

public class PostViewModel extends ViewModel {

    public static final String TAG = "PostViewModel";


    public MutableLiveData<PostList> postListMutableLiveData = new MutableLiveData<>();
    public MutableLiveData<PostList> postListByLabelMutableLiveData = new MutableLiveData<>();
    public MutableLiveData<String> finalURL = new MutableLiveData<>();
    public MutableLiveData<String> token = new MutableLiveData<>();

    public void getPosts(){


        if (token.getValue() != "") {
            finalURL.setValue(finalURL.getValue() + "&pageToken=" + token.getValue());
        }
        if (token == null) {
            return;
        }

        PostsClient.getINSTANCE().getPostList().enqueue(new Callback<PostList>() {
            @Override
            public void onResponse(@NotNull Call<PostList> call, @NotNull Response<PostList> response) {

                PostList list = response.body();

                if (list.getItems() != null) {
                    token.setValue(list.getNextPageToken());
                    postListMutableLiveData.setValue(list);
                }

                Log.i(TAG,response.body().getItems().toString());
            }

            @Override
            public void onFailure(Call<PostList> call, Throwable t) {
                Log.e(TAG,t.getMessage());
            }
        });

    }


    public void getPostListByLabel(){

        PostsByLabelClient.getINSTANCE().getPostListByLabel(finalURL.getValue()).enqueue(new Callback<PostList>() {
            @Override
            public void onResponse(Call<PostList> call, Response<PostList> response) {
                postListByLabelMutableLiveData.setValue(response.body());
            }

            @Override
            public void onFailure(Call<PostList> call, Throwable t) {

            }
        });
    }
}

HomeFragment Class "The main page"

public class HomeFragment extends Fragment {

    private PostViewModel postViewModel;
    public static final String TAG = "HomeFragment";
    private RecyclerView recyclerView;
    private PostAdapter postAdapter;
    private List<Item> itemArrayList;
    private boolean isScrolling = false;
    private int currentItems, totalItems, scrollOutItems, selectedIndex;

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {

        postViewModel = new ViewModelProvider(this).get(PostViewModel.class);
        postViewModel.getPosts();

        View root = inflater.inflate(R.layout.fragment_home, container, false);

        itemArrayList = new ArrayList<>();

        recyclerView = root.findViewById(R.id.homeRecyclerView);
        postAdapter = new PostAdapter(getContext(),itemArrayList);

        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(linearLayoutManager);
        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext()
                , linearLayoutManager.getOrientation());
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.addItemDecoration(dividerItemDecoration);
        recyclerView.setAdapter(postAdapter);

//                textView.setText(s);
                postViewModel.postListMutableLiveData.observe(HomeFragment.this, new Observer<PostList>() {
                    @Override
                    public void onChanged(PostList postList) {
                        itemArrayList.addAll(postList.getItems());
                        postAdapter.notifyDataSetChanged();
                    }
                });


        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                    isScrolling = true;



            }

            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (dy > 0) {
                    currentItems = linearLayoutManager.getChildCount();
                    totalItems = linearLayoutManager.getItemCount();
                    scrollOutItems = linearLayoutManager.findFirstVisibleItemPosition();
                    if (isScrolling && (currentItems + scrollOutItems == totalItems)) {
                        isScrolling = false;
                        postViewModel.getPosts();
                        postAdapter.notifyDataSetChanged();


                    }
                }

            }
        });


        return root;

    }
}

’More explanation

on PostViewModel I created one variable

public MutableLiveData<String> token = new MutableLiveData<>();

This token that represents a new page/response will carry "each page has a list / ten new posts"

on HomeFragment

I created three integer values

private int currentItems, totalItems, scrollOutItems, selectedIndex;

and one boolean

private boolean isScrolling = false;

then I used recyclerView.addOnScrollListener

with this way to load the next ten posts, but it's not working like I said before, its loading the same result/list

The result on imgur.com
like image 295
Dr Mido Avatar asked Feb 11 '21 14:02

Dr Mido


2 Answers

After hundreds of tries, I finally solved it, here's the solution of problem

Firstly I changed the GET method in the API PostInterface and make it take @URL instead of @Query KEY like this

public interface PostInterface {

    @GET
    Call<PostList> getPostList(@Url String URL);
}

Secondary I edited the PostsClient removed final from BASE_URL private static String BASE_URL and create a setter & getter for BASE URL & KEY

public static String getKEY() {
        return KEY;
    }

    public static String getBaseUrl() {
        return BASE_URL;
    }

Thirdly & finally I moved this if statement for the token checker after the response

public void getPosts(){

        Log.e(TAG,finalURL.getValue());

        PostsClient.getINSTANCE().getPostList(finalURL.getValue()).enqueue(new Callback<PostList>() {
            @Override
            public void onResponse(@NotNull Call<PostList> call, @NotNull Response<PostList> response) {

                PostList list = response.body();


                if (list.getItems() != null) {

                    Log.e(TAG,list.getNextPageToken());
                    token.setValue(list.getNextPageToken());
                    postListMutableLiveData.setValue(list);

                }
                if (token.getValue() == null || !token.getValue().equals("") ) {
                    finalURL.setValue(finalURL.getValue() + "&pageToken=" + token.getValue());
                }


//                Log.i(TAG,response.body().getItems().toString());
            }

            @Override
            public void onFailure(Call<PostList> call, Throwable t) {
                Log.e(TAG,t.getMessage());
            }
        });

    }
like image 186
Dr Mido Avatar answered Sep 28 '22 02:09

Dr Mido


You mutate the finalURL value each time you want to grab new posts with:

if (token.getValue() != "") {
    finalURL.setValue(finalURL.getValue() + "&pageToken=" + token.getValue());
}

Here you use finalURL.getValue() which already contains the old token value (of the previous page). This is OK for the first page.

When you come back for the next page, you get the value of the finalURL and concatenate the new token to it, although the current finalURL already contains the value of the last token. So now finalURL contains a couple of tokens, I think the API can take the first one which is the toke of the previous page (So, you'll get the same list of posts).

So you need to change that with a constant value of the baseURL:

final String baseURL = "" // add base URL which is basically the initial value of the `finalURL`
if (token.getValue() != "") {
    finalURL.setValue(baseURL + "&pageToken=" + token.getValue());
}

Side Note:

If you intend to compare Strings with token.getValue() != "", then you need to change that with String .equals() or for checking empty String use isEmpty() method.

like image 1
Zain Avatar answered Oct 16 '22 18:10

Zain