Hamid Reza
Hamid Reza

Reputation: 664

Android Leanback: How to update nested rows item in RowsSupportFragment

Hey Guys

I'm working on androidTV application using leanback library.

I should show list of categories that each category has it's own list of contents. For this approach leanback offered RowsSupportFragment that you can show this type of UI inside that.

enter image description here

Here I am using Room + LiveData + Retrofit + Glide to perform and implement the screen, but the issue is here, the api will not pass content cover images directly, so developer should download each content cover image, store it and then show covert over the content.

Every thing is working but at the first time, If there is no cover image for content, I will download the cover and store it, but content will not be triggered to get and show image. Using notifyItemRangeChanged and methods like this will blink and reset the list row so this is not a good solution.

This is my diffUtils that I'm using, one for category list, one for each contents list.

private val diffCallback = object : DiffCallback<CardListRow>() {
    override fun areItemsTheSame(oldItem: CardListRow, newItem: CardListRow): Boolean {
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: CardListRow, newItem: CardListRow): Boolean {
        return oldItem.cardRow.contents?.size == newItem.cardRow.contents?.size
    }
}

private val contentDiffCallback = object : DiffCallback<ContentModel>() {
    override fun areItemsTheSame(oldItem: ContentModel, newItem: ContentModel): Boolean {
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: ContentModel, newItem: ContentModel): Boolean {
        return oldItem.hashCode() == newItem.hashCode()
    }

}

As I said, for storage I'm using room, retrieving data as LiveData and observing them in my fragment and so on. I have not posted all the codes for summarization.

If you have any idea or similar source code, I would appreciate it. Thanks

Edit: Fri Dec 2 --- add some more details

This is my live-data observer that holds and observe main list on categories and datas

private fun initViewModel() {
    
            categoriesViewModel.getCategoriesWithContent().observe(viewLifecycleOwner) { result ->
                val categoryModelList = MergedContentMapper().toCategoryModelList(result)
                initData(categoryModelList)
            }
    }

And this is the row creation scenario using ArrayObjectAdapter

private fun initData(categoryModelList: List<CategoryModel>) {
    showLoading(false)
    createRows(categoryModelList)
}

private fun createRows(categoryModelList: List<CategoryModel>) {
    val rowItemsList: MutableList<CardListRow> = arrayListOf()
    // create adapter for the whole fragment. It displays Rows.
    categoryModelList.forEach { categoryModel ->
        // create adapter for each row that can display CardView using CardPresenter
        val cardListRow = createCardRow(categoryModel)
        // add card list rows into list
        rowItemsList.add(cardListRow)
    }

    // set item with diff util
    rowsAdapter.setItems(rowItemsList, diffCallback)

}

private fun createCardRow(categoryModel: CategoryModel): CardListRow {
    val contentList = categoryModel.contents ?: emptyList()
    val cardListRowsAdapter = ArrayObjectAdapter(CardPresenterSelector(context, this))
    cardListRowsAdapter.setItems(contentList, contentDiffCallback)
    val headerItem = HeaderItem(categoryModel.title)
    return CardListRow(headerItem, cardListRowsAdapter, categoryModel)
}

Upvotes: 0

Views: 987

Answers (1)

admqueiroga
admqueiroga

Reputation: 130

Your code looks correct, but it's missing the part where you tell the Presenter what changed on your items so it can change only that piece of data and doesn't need to re-bind the entire content avoiding the blink.

After your DiffCallback detects that the items are the same but the content has changed it will call its getChangePayload() function to gather details about the changes to pass them to the Presenter. To achieve that you need to do the following changes:

First, you need to override the DiffCallback.getChangePayload() function to something like this:

override fun getChangePayload(oldItem: ListRow, newItem: ListRow): Any {
    return when {
        oldItem.headerItem.name != newItem.headerItem.name -> "change_title"
        else -> "change_items"
    }
}

With that your ListRowPresenter will receive the information of what changed in the ListRowPresenter.onBindViewHolder() overload that receives a payload list (returned by your DiffCallback) like so:


override fun onBindViewHolder(
        viewHolder: Presenter.ViewHolder?,
        item: Any?,
        payloads: MutableList<Any>?
    ) {
    when {
        payloads == null || payloads.isEmpty() -> {
            // Your DiffCallback did not returned any information about what changed.
            super.onBindViewHolder(viewHolder, item, payloads)
        }
        "change_title" in payloads -> getRowViewHolder(viewHolder)?.let {
            headerPresenter.onBindViewHolder(it.headerViewHolder, item)
        }
        "change_items" in payloads -> {
            val newItems = ((item as? ListRow)?.adapter as? ArrayObjectAdapter)?.unmodifiableList<Item>()
            when (val listRowAdapter = (getRowViewHolder(viewHolder).row as? ListRow)?.adapter) {
                is ArrayObjectAdapter -> listRowAdapter.setItems(newItems, null)
                else -> super.onBindViewHolder(viewHolder, item, payloads)
            }
        }
        else -> {
            // If you don't know what changed just delegate to the super.
            super.onBindViewHolder(viewHolder, item, payloads)
        }
    }
}

Customize the implementation of DiffCallback.getChangePayload() to your needs. You can return a list of changes in this function and treat all of them in your ListRowPresenter.onBindViewHolder().

I recently wrote a blog post with samples that might help.

Upvotes: 0

Related Questions