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