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<String> token = new MutableLiveData<>();
这个代表一个新页面/响应的令牌将携带“每个页面都有一个列表/十个新帖子”
在 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-diushenovPostsClient.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);
次要我编辑了 PostsClient
从 BASE_URL
private static String BASE_URL
中删除的最终结果,并为 BASE URL & KEY
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】:考虑使用新的分页库。这是 sample 和 documentation。它表现得更好,您不必手动跟踪您在滚动中的位置。通过遵循示例,我发现实现这一点相当容易,尽管它有很多样板代码。## 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&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() + "&pageToken=" + token.getValue()); if (token == null) return;
以上是关于recyclerView.addOnScrollListener - “使用 MVVM 改造分页”正在加载相同的响应/列表的主要内容,如果未能解决你的问题,请参考以下文章