2

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
Dr Mido
  • 2,414
  • 4
  • 32
  • 72
  • Could you please provide your Adapter code? – Soumik Bhattacharjee Feb 14 '21 at 08:04
  • @soumik-bhattacharjee The Post Adapter class is about 385 lines of codes!, If I added it to my question it will be confusing to users , it would make it more complicated and I think it has nothing to do with the problem at hand – Dr Mido Feb 14 '21 at 08:28
  • In Recycler view, it recycles the views, while doing that it sometimes can't cast data on the recycled view while recycling. For tackling it sometimes we need to make some changes in our adapter. That's why I was asking for the adapter code. But if the data is not coming from the api, then this case is out of context. – Soumik Bhattacharjee Feb 14 '21 at 09:40
  • PostsClient.getINSTANCE().getPostList() you are not passing nextpageToken to this function, how does it know what page to load? – Rinat Diushenov Feb 18 '21 at 12:48
  • @rinat-diushenov `PostsClient.getINSTANCE().getPostList().enqueue` this callback of retrofit to get response, I already getting the token inside it, please focus on this part again `if (list.getItems() != null) { token.setValue(list.getNextPageToken()); postListMutableLiveData.setValue(list); }` – Dr Mido Feb 18 '21 at 13:08
  • Can look at value "token" every time u scroll, though logging or debug mode, does it get updated evrytime? Assuming that nextPageToken is some kind of page indicator. – Rinat Diushenov Feb 18 '21 at 13:29
  • if (token == null) { return; } this will never be true because you instatiate token variable at class creation. So this part is kinda redundant. Instead you should check it's value, which can be null – Rinat Diushenov Feb 18 '21 at 13:30
  • @rinat-diushenov I am done with that, I successfully get the first token on logcat on scroll it's "CgkIChigkoPLwy0Quf_O-Lrax8w7" something like that – Dr Mido Feb 18 '21 at 14:11
  • Ok. But does it get updated once you scroll and request next page? – Rinat Diushenov Feb 18 '21 at 16:52

2 Answers2

1

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.

Zain
  • 37,492
  • 7
  • 60
  • 84
  • I didn't use `final URL` on the homeFragment, but thanks for this note, I declared it to use it on other fragments which every fragment it displays the blogger items by Label for exmaple : in AccessoryFragment I geeting posts filtired by label "Accessory" and I use it like this `ostViewModel.finalURL.setValue(PostsByLabelClient.getBaseUrl() + "search?q=label:Accessory&key=" + PostsByLabelClient.getKEY());` to relate show my edit question again I provided the `PostsClient` Class – Dr Mido Feb 20 '21 at 17:34
  • to use the `final URL` as you told me I edited my `PostsClient` andd add setter & getter to `BASE_URL` after deleted `final` word, and I used it in `PostViewModel` like this but the app is crashed because the token is null `java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.isEmpty()' on a null object reference at com.test.dummyappv3.ui.main.PostViewModel.getPosts(PostViewModel.java:30) at com.test.dummyappv3.ui.home.HomeFragment.onCreateView(HomeFragment.java:46)` – Dr Mido Feb 20 '21 at 17:40
  • @DrMido can you elaborate more .. the NPE appears on a String that calls `isEmpty()` within HomeFragment.. I can't see in your shared code where did you call `isEmpty()` in `HomeFragment` – Zain Feb 20 '21 at 18:05
  • Also why not passing token to Retrofit query as method parameters.. something like in https://stackoverflow.com/questions/36730086/retrofit-2-url-query-parameter/36730379 – Zain Feb 20 '21 at 18:19
  • sorry I forgot to add the code on my last replay it's be like this `if (token.getValue().isEmpty()) { PostsClient.setBaseUrl(PostsClient.getBaseUrl() + "&pageToken=" + token.getValue()); } if (token == null) { return; }` – Dr Mido Feb 20 '21 at 18:57
  • I know about @Query & @PATH in retrofit, and I was tried to use it in my `PostClient` but it's not working with this case, so I used @Url String URL the best way – Dr Mido Feb 20 '21 at 19:01
  • 1
    I see you check that `token == null` after this condition `if (token.getValue().isEmpty())`.. you need to check null-ability first like `if (token == null) { return; } if (token.getValue().isEmpty()) { PostsClient.setBaseUrl(PostsClient.getBaseUrl() + "&pageToken=" + token.getValue()); }` – Zain Feb 20 '21 at 19:22
0

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());
            }
        });

    }
Dr Mido
  • 2,414
  • 4
  • 32
  • 72