12

Which is the best way to update a single element when using the new paging library?

Let's say we have the Paging with network google sample using the PageKeyedSubredditDataSource. Imagine we want to make a change of a single element of RedditPost. So, we want to check if it is in the list and if so, update it. The update should not be as easy as calling invalidate() which will make a call to the first page (maybe the RedditPost is in the 5th page. We don't want to update all elements, just one).

Damia Fuentes
  • 5,308
  • 6
  • 33
  • 65
  • 1
    see https://developer.android.com/reference/android/arch/paging/DataSource, section `Updating Paged Data`, last paragraph – pskink May 31 '18 at 04:16
  • 1
    Thanks. But is there any way without using in-memory? – Damia Fuentes May 31 '18 at 14:23
  • are you using `PagedListAdapter`? if so, whats your `DiffCallback` like? – pskink May 31 '18 at 14:32
  • Yes. val POST_COMPARATOR = object : DiffUtil.ItemCallback() { override fun areContentsTheSame(oldItem: UserEntity, newItem: UserEntity): Boolean = oldItem == newItem override fun areItemsTheSame(oldItem: UserEntity, newItem: UserEntity): Boolean = oldItem.id == newItem.id } – Damia Fuentes May 31 '18 at 15:18
  • @DamiaFuentes did you succeed? I can successfully updated the List is jumping to beginning every time items are updated. – Keivan Esbati Jun 30 '18 at 08:25
  • 1
    I seriously don't get why room is using LiveData all over the place, but the paging library needs get fixed data or we need to implement some sort of in memory hacky way to invalidate all the datasources. Any solutions here? My database changes all the time and therefore emits liveData values, which does not work well will paging library. – JacksOnF1re Sep 26 '18 at 09:19
  • 1
    @KeivanEsbati Check the answer! – Damia Fuentes Sep 27 '18 at 06:12
  • @DamiaFuentes I will! Thanks – Keivan Esbati Sep 27 '18 at 08:17

1 Answers1

2

Please note that all this works over the Paging with network google sample. Although that, the idea is there.

@Sarquella helped me with this solution. Add this classes to your project. Basically we are extending ViewHolder to be LifeCycle Owner, as it is already done by default with Activities and Fragments.

The LifecycleViewHolder:

abstract class LifecycleViewHolder(itemView: View) :
        RecyclerView.ViewHolder(itemView),
        LifecycleOwner {

    private val lifecycleRegistry = LifecycleRegistry(this)

    fun onAttached() {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
    }

    fun onDetached() {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
    }

    override fun getLifecycle(): Lifecycle = lifecycleRegistry
}

LifecycleOwner is a single method interface that denotes that the class has a Lifecycle. You can find more information here.

The LifecyclePagedListAdapter:

abstract class LifecyclePagedListAdapter<T, VH : LifecycleViewHolder>(diffCallback: DiffUtil.ItemCallback<T>) :
        PagedListAdapter<T, VH>(diffCallback) {

    override fun onViewAttachedToWindow(holder: VH) {
        super.onViewAttachedToWindow(holder)
        holder.onAttached()
    }

    override fun onViewDetachedFromWindow(holder: VH) {
        super.onViewDetachedFromWindow(holder)
        holder.onDetached()
    }
}

The LifecycleAdapter (in the case you need it):

abstract class LifecycleAdapter<VH : LifecycleViewHolder> :
        RecyclerView.Adapter<VH>() {

    override fun onViewAttachedToWindow(holder: VH) {
        super.onViewAttachedToWindow(holder)
        holder.onAttached()
    }

    override fun onViewDetachedFromWindow(holder: VH) {
        super.onViewDetachedFromWindow(holder)
        holder.onDetached()
    }
}

Then, extends MyAdapter to LifecyclePagedListAdapter<MyEntity, LifecycleViewHolder>(MY_COMPARATOR) and MyViewHolder to LifecycleViewHolder(view). You'll have to complete your classes based on what we have changed, accordingly. Now we can observe to a liveData object on MyViewHolder class. So we can add this to MyViewHolder class (assuming you're using Dependency Injection). Basically, we'll do the same we do for Fragments or Activities:

private lateinit var myViewModel: MyViewModel

init {
    (itemView.context as? AppCompatActivity)?.let{
        myViewModel = ViewModelProviders.of(it).get(MyViewModel::class.java)
    }
}

Then, inside the bind() method:

fun bind(myCell: MyEntity?) {
    myViewModel.myLiveData.observe(this, Observer {
        // Buala!! Check if it is the cell you want to change and update it.
        if (it != null && myCell != null && it.id == myCell.id) {
           updateCell(it)
        }
    })
}
Damia Fuentes
  • 5,308
  • 6
  • 33
  • 65
  • 2
    Hi Damia, might I ask is there any sample code that demonstrates all the necessary changes? I feel lost when I was going through the bind() method and updateCell. – Tony Thompson Dec 24 '18 at 04:47
  • @Damia Fuentes Could you please provide any working sample for updating a specific page. – Badhrinath Canessane Mar 11 '19 at 21:08
  • @TonyThompson You can find a library implementing all those changes as well as a sample app here: https://github.com/Sarquella/LifecycleCells – Sarquella Nov 26 '19 at 19:13
  • @BadhrinathCanessane You can find a library implementing all those changes as well as a sample app here: https://github.com/Sarquella/LifecycleCells – Sarquella Nov 26 '19 at 19:13
  • For some reason, the observer is never called. I don't understand why – isaacHerb Dec 06 '21 at 20:45