5

I have a simple list of Pokemon in a RecyclerView with just the Pokemon's name and a "Favorite" ToggleButton. I'm using the Paging Library from Android JetPack with a PageKeyedDataSource to retrieve small chunks of Pokemon and display them to the user. I only want the data to persist as long as the Activity is not destroyed (i.e. I do NOT want to persist the data to Room or a database, but rather have it persist as long as the ViewModel is alive).

Screenshot of app

I want to be able to click the heart button on any Pokemon item and update the "isFavorite" field in the SimplePokemon model to true or false. From my understanding, if I wanted to change a single item from that PagedList, I would need to invalidate the DataSource and that should, theoretically, generate a new LiveData of a PagedList that could be fed to the Adapter and shown on screen.

Question: How can I update a single item from a PagedList using the Paging Library without the need for Room or some other database?

In the future, I want to scale this solution to a social media feed where users can like posts, but I don't know if storing social feed items in a database such as Room is necessary (or efficient) since those feed items are constantly changing. So I opted for storing them in the ViewModel and then clearing them every time the user quits the app.

Here is my code so far:

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

I also want to make sure that the RecyclerView does not jump to the top every time the DataSource is updated.

The ideal scenario would be to keep a List of Pokemon as long as the Activity is alive and be able to update individual Pokemon items locally. Theoretically, I would also send a POST request to the backend to update the Pokemon in the back-end, but I'm just trying to keep the question simple.

Any help would be sincerely appreciated.

Calvin Rai
  • 816
  • 1
  • 8
  • 17

1 Answers1

0

To Update single item in PagedListAdapter i have added this method in PagedListAdapter. in my case.

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


    }
karan
  • 95
  • 1
  • 2
  • 9