recyclerView.addOnScrollListener - “使用 MVVM 改造分页”正在加载相同的响应/列表

Posted

技术标签:

【中文标题】recyclerView.addOnScrollListener - “使用 MVVM 改造分页”正在加载相同的响应/列表【英文标题】:recyclerView.addOnScrollListener - "retrofit pagination with MVVM" is loading the same response/list 【发布时间】:2021-05-15 07:21:12 【问题描述】:

我在我的应用程序中使用博客 API、改造和 MVVM,我试图在用户滚动时使用分页来加载更多帖子,这里发生的问题是响应加载它自己“相同的列表/相同的十个帖子是重新加载”

这是我的代码

PostsClient 类

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 类“主页”

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;

    

'更多解释

在 PostViewModel 上 我创建了一个变量

public MutableLiveData&lt;String&gt; token = new MutableLiveData&lt;&gt;();

这个代表一个新页面/响应的令牌将携带“每个页面都有一个列表/十个新帖子”

在 HomeFragment 上

我创建了三个整数值

private int currentItems, totalItems, scrollOutItems, selectedIndex;

一个布尔值

private boolean isScrolling = false;

然后我用recyclerView.addOnScrollListener

用这种方式加载接下来的十个帖子,但它不像我之前说的那样工作,它加载相同的结果/列表

The result on imgur.com

【问题讨论】:

能否提供您的适配器代码? @soumik-bhattacharjee Post Adapter 类大约有 385 行代码!,如果我将它添加到我的问题中,它会让用户感到困惑,它会使它变得更加复杂,我认为它什么都没有解决手头的问题 在 Recycler 视图中,它会回收视图,而这样做有时无法在回收时将数据投射到回收的视图上。为了解决这个问题,有时我们需要对适配器进行一些更改。这就是我要求适配器代码的原因。但如果数据不是来自 api,那么这种情况就断章取义了。 PostsClient.getINSTANCE().getPostList() 你没有将 nextpageToken 传递给这个函数,它怎么知道要加载哪个页面? @rinat-diushenov PostsClient.getINSTANCE().getPostList().enqueue这个retrofit的回调得到响应,我已经拿到里面的token了,请再次关注这部分if (list.getItems() != null) token.setValue(list.getNextPageToken()); postListMutableLiveData.setValue(list); 【参考方案1】:

经过数百次尝试,我终于解决了,这是问题的解决方案

首先我更改了 API 中的 GET 方法 PostInterface 并使其采用 @URL 而不是 @Query 这样的 KEY

public interface PostInterface 

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

次要我编辑了 PostsClientBASE_URL private static String BASE_URL 中删除的最终结果,并为 BASE URL & KEY

创建了一个 setter 和 getter
public static String getKEY() 
        return KEY;
    

    public static String getBaseUrl() 
        return BASE_URL;
    

第三次和最后一次我在响应之后为令牌检查器移动了这个 if 语句

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

    

【讨论】:

【参考方案2】:

考虑使用新的分页库。这是 sampledocumentation。它表现得更好,您不必手动跟踪您在滚动中的位置。通过遵循示例,我发现实现这一点相当容易,尽管它有很多样板代码。## Heading ##

【讨论】:

这个例子是 kotlin 和我的 java 项目,无论如何谢谢你的建议,我会用 java 搜索一些例子【参考方案3】:

每次您想要获取新帖子时,您都会更改 finalURL 值:

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

这里你使用finalURL.getValue(),它已经包含旧的令牌值(上一页的)。这对于第一页来说是可以的。

当您返回下一页时,您将获得finalURL 的值并将新令牌连接到它,尽管当前finalURL 已经包含最后一个令牌的值。所以现在finalURL 包含几个标记,我认为 API 可以采用第一个标记,它是上一页的标记(所以,你会得到相同的帖子列表)。

所以你需要用 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());

旁注:

如果您打算将字符串与token.getValue() != "" 进行比较,则需要将其更改为字符串.equals() 或检查空字符串使用isEmpty() 方法。

【讨论】:

我没有在 homeFragment 上使用final URL,但是感谢这个注释,我声明它可以在其他片段上使用它,每个片段都按标签显示博客项目,例如:在 AccessoryFragment我收到了由标签“附件”过滤的帖子,我像这样使用它ostViewModel.finalURL.setValue(PostsByLabelClient.getBaseUrl() + "search?q=label:Accessory&amp;key=" + PostsByLabelClient.getKEY()); 再次显示我的编辑问题我提供了PostsClient 按照你告诉我的那样使用final URL,我编辑了我的PostsClient,并在删除final字后将setter和getter添加到BASE_URL,我在PostViewModel中使用它就像这样但是应用程序崩溃了,因为令牌为空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) @DrMido 你能详细说明一下吗.. NPE 出现在 HomeFragment 中调用isEmpty() 的字符串上。我在您的共享代码中看不到您在@987654343 中调用isEmpty() 的位置@ 另外,为什么不将令牌作为方法参数传递给 Retrofit 查询......类似于***.com/questions/36730086/… 对不起,我上次重播时忘记添加代码了,就像这样if (token.getValue().isEmpty()) PostsClient.setBaseUrl(PostsClient.getBaseUrl() + "&amp;pageToken=" + token.getValue()); if (token == null) return;

以上是关于recyclerView.addOnScrollListener - “使用 MVVM 改造分页”正在加载相同的响应/列表的主要内容,如果未能解决你的问题,请参考以下文章