使用 Google 的分页库更新 PagedList 中的单个项目

Posted

技术标签:

【中文标题】使用 Google 的分页库更新 PagedList 中的单个项目【英文标题】:Update a Single Item in a PagedList using Google's Paging Library 【发布时间】:2020-06-01 22:24:27 【问题描述】:

我在 RecyclerView 中有一个简单的 Pokemon 列表,其中只有 Pokemon 的名称和“Favorite”切换按钮。我正在使用带有 PageKeyedDataSource 的 android JetPack 的分页库来检索小块 Pokemon 并将它们显示给用户。只要 Activity 没有被破坏,我只希望数据保持不变(即,我不想将数据保持到 Room 或数据库,而是只要 ViewModel 还活着就让它保持不变)。

Screenshot of app

我希望能够单击任何 Pokemon 项目上的心形按钮并将 SimplePokemon 模型中的“isFavorite”字段更新为 truefalse。据我了解,如果我想更改该 PagedList 中的单个项目,我需要使 DataSource 无效,并且理论上应该生成一个 PagedList 的新 LiveData,可以将其提供给 Adapter 并显示在屏幕上。

问题:如何在不需要 Room 或其他数据库的情况下使用分页库更新 PagedList 中的单个项目?

将来,我想将此解决方案扩展到社交媒体供稿,用户可以在其中点赞帖子,但我不知道将社交供稿项存储在诸如 Room 之类的数据库中是否必要(或有效),因为这些供稿物品是不断变化的。所以我选择将它们存储在 ViewModel 中,然后在每次用户退出应用时清除它们。

到目前为止,这是我的代码:

SimplePokemon.kt:

data class SimplePokemon(
    @SerializedName("name") val name: String,
    @SerializedName("url") val url: String,
    var isFavorite: Boolean = false
)

PokemonViewModel.kt:

class PokemonViewModel(application: Application) : AndroidViewModel(application) 

    private val config = PagedList.Config.Builder()
        .setPageSize(20)
        .setEnablePlaceholders(false)
        .build()

    private fun initializedPagedListBuilder(config: PagedList.Config): LivePagedListBuilder<String, SimplePokemon> 
        val dataSourceFactory = object : DataSource.Factory<String, SimplePokemon>() 
            override fun create(): DataSource<String, SimplePokemon> 
                return PokemonDataSource()
            
        
        return LivePagedListBuilder<String, SimplePokemon>(dataSourceFactory, config)
    

    fun pokemonPagedListLiveData(): LiveData<PagedList<SimplePokemon>> 
        return initializedPagedListBuilder(config).build()
    

PokemonAdapter.kt:

class PokemonAdapter :
    PagedListAdapter<SimplePokemon, PokemonAdapter.PokemonViewHolder>(PokemonDiffUtil()) 

    inner class PokemonViewHolder(v: View) : RecyclerView.ViewHolder(v) 
        private val pokemonNameTextView: TextView = v.findViewById(R.id.pokemon_name_text_view)
        private val pokemonFavoriteToggle: ToggleButton =
            v.findViewById(R.id.pokemon_favorite_toggle_button)

        fun bind(data: SimplePokemon) 
            pokemonNameTextView.text = data.name
            pokemonFavoriteToggle.isChecked = data.isFavorite
        
    

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PokemonViewHolder 
        val view =
            LayoutInflater.from(parent.context).inflate(R.layout.item_simple_pokemon, parent, false)
        return PokemonViewHolder(view)
    

    override fun onBindViewHolder(holder: PokemonViewHolder, position: Int) 
        val item = getItem(position)
        item?.let  holder.bind(it) 
    

PokemonDataSource.kt:

class PokemonDataSource : PageKeyedDataSource<String, SimplePokemon>() 

    private val api = NetworkService.pokemonNetworkInterface

    override fun loadInitial(
        params: LoadInitialParams<String>,
        callback: LoadInitialCallback<String, SimplePokemon>
    ) 
        api.getPokemon().enqueue(object : Callback<PokeResponse<List<SimplePokemon>>> 

            override fun onFailure(call: Call<PokeResponse<List<SimplePokemon>>>?, t: Throwable?) 
                Log.e("PokemonDataSource", "Failed to fetch data!")
            

            override fun onResponse(
                call: Call<PokeResponse<List<SimplePokemon>>>?,
                response: Response<PokeResponse<List<SimplePokemon>>>
            ) 
                val listing = response.body()
                val pokemon = listing?.results
                callback.onResult(pokemon ?: listOf(), listing?.previous, listing?.next)
            
        )
    

    override fun loadAfter(
        params: LoadParams<String>,
        callback: LoadCallback<String, SimplePokemon>
    ) 
        api.getPokemon(url = params.key)
            .enqueue(object : Callback<PokeResponse<List<SimplePokemon>>> 
                override fun onFailure(
                    call: Call<PokeResponse<List<SimplePokemon>>>?,
                    t: Throwable?
                ) 
                    Log.e("PokemonDataSource", "Failed to fetch data! Oh Noooo!")
                

                override fun onResponse(
                    call: Call<PokeResponse<List<SimplePokemon>>>?,
                    response: Response<PokeResponse<List<SimplePokemon>>>
                ) 
                    val listing = response.body()
                    val pokemon = listing?.results
                    callback.onResult(pokemon ?: listOf(), listing?.next)
                
            )
    

    override fun loadBefore(
        params: LoadParams<String>,
        callback: LoadCallback<String, SimplePokemon>
    ) 
        api.getPokemon(url = params.key)
            .enqueue(object : Callback<PokeResponse<List<SimplePokemon>>> 
                override fun onFailure(
                    call: Call<PokeResponse<List<SimplePokemon>>>?,
                    t: Throwable?
                ) 
                    Log.e("PokemonDataSource", "Failed to fetch data! Oh Noooo!")
                

                override fun onResponse(
                    call: Call<PokeResponse<List<SimplePokemon>>>?,
                    response: Response<PokeResponse<List<SimplePokemon>>>
                ) 
                    val listing = response.body()
                    val pokemon = listing?.results
                    callback.onResult(pokemon ?: listOf(), listing?.previous)
                
            )
    

我还想确保每次更新 DataSource 时 RecyclerView 都不会跳到顶部。

理想的情况是,只要 Activity 处于活动状态,就保留一个 Pokemon 列表,并且能够在本地更新单个 Pokemon 项目。理论上,我还会向后端发送一个 POST 请求以更新后端的口袋妖怪,但我只是想让问题保持简单。

任何帮助将不胜感激。

【问题讨论】:

你找到解决办法了吗? 【参考方案1】:

为了更新 PagedListAdapter 中的单个项目,我在 PagedListAdapter 中添加了这个方法。 就我而言。

    // Adapter Class
    public class ChatHistoryAdapter extends PagedListAdapter<ChatHistoryEntity, ChatHistoryAdapter.ViewHolder> 
       
        OnClickListener<ChatHistoryEntity> listener;
    Context mContext;


    public ChatHistoryAdapter(Context context) 
        super(DIFF_CALLBACK);
        mContext = context;
    

    public void setOnClickListener(OnClickListener<ChatHistoryEntity> listener) 
        this.listener = listener;
    


    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) 
        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
        ItemChatHistoryBinding binding =
                ItemChatHistoryBinding.inflate(layoutInflater,parent,false);

        return new ViewHolder(binding);
    

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) 
        ChatHistoryEntity current = getChat(position);
        holder.binding.tvSender.setText(current.getName_or_mobile_or_groupName());
        holder.binding.tvMessage.setText(current.getSubject());
//        holder.binding.tvTimestamp.setText(current.getMessage_date_time().toString());

//        holder.tv_sender.setText(current.getContact_name_or_mobile_or_groupName());
//        holder.tv_message.setText(current.getMessage());

        if (current.isSelected())
               holder.binding.mainLayout.setBackgroundColor(ContextCompat.getColor(mContext, R.color.item_selection));
//            holder.binding.mainLayout.setBackgroundColor();
        else 
            holder.binding.mainLayout.setBackgroundColor(0);
        


        holder.Bind(current,listener,position);

    

    public ChatHistoryEntity getChat(int position) 
        return getItem(position);
    

    public void updateItemSelection(int position,boolean isSelect)
        ChatHistoryEntity current = getChat(position);
        current.setSelected(isSelect);
        notifyDataSetChanged();
    

    public void clearSelection()
        List<ChatHistoryEntity> collect =  StreamSupport.stream(getCurrentList().snapshot())
                .peek(o -> o.setSelected(false))
                .collect(Collectors.toList());

//        submitList(PagedList<ChatHistoryEntity> list = new PagedList<>());
        notifyDataSetChanged();
    


    private static final DiffUtil.ItemCallback<ChatHistoryEntity> DIFF_CALLBACK =
            new DiffUtil.ItemCallback<ChatHistoryEntity>() 
                @Override
                public boolean areItemsTheSame(@NonNull ChatHistoryEntity oldItem,
                                               @NonNull ChatHistoryEntity newItem) 
                    return oldItem.getId() == newItem.getId();
                

                @Override
                public boolean areContentsTheSame(@NonNull ChatHistoryEntity    oldItem,
                                                  @NonNull ChatHistoryEntity   newItem) 
                    return oldItem.getId() == newItem.getId()
                            && oldItem.getName_or_mobile_or_groupName()
                            .equalsIgnoreCase(newItem.getName_or_mobile_or_groupName());
                
            ;

    public static class ViewHolder extends RecyclerView.ViewHolder 

        ItemChatHistoryBinding binding;

        public ViewHolder(ItemChatHistoryBinding binding) 
            super(binding.getRoot());
            this.binding =binding;

        


        void Bind(ChatHistoryEntity chatEntity,   OnClickListener<ChatHistoryEntity> mOnCharacterClick,
                  int position) 

            binding.mainLayout.setOnClickListener(v ->
                    mOnCharacterClick.OnClick(chatEntity,"",position));
        
    

    

    // Activity or fragument Class.
     public class MainActivity extends AppCompatActivity 

         mAdapter.setOnClickListener((currentObj, mode, position) -> 
             // This is how you update single item in PagedListAdapter list.
             mAdapter.updateItemSelection(position,!currentObj.isSelected())
        );


    

【讨论】:

以上是关于使用 Google 的分页库更新 PagedList 中的单个项目的主要内容,如果未能解决你的问题,请参考以下文章

使用分页库时,观察者显示列表大小为零

在分页库 3 中恢复滚动位置

分页库 - 网络 + db 的边界回调,API 获取页面和大小

分页库使用示例的 Proguard 问题(在 Google Codelabs 中)

Codeigniter 分页链接按降序/倒序排列?

如何在codeigniter中的单个函数中运行多个分页