question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

RecyclerView won't update view after data change.

See original GitHub issue

Hi,

So what I tried to do is I set a controller to EpoxyRecyclerView, set a data to the controller and call requestModelBuild(). But, the problem is that the EpoxyRecyclerView won’t update its UI for some reason.

This is what my code looks like.

CategorizedPromotionsFragment.kt

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        fragment_promotions_categorized_recycler_view.setController(promotionController)
        fragment_promotions_categorized_recycler_view.isNestedScrollingEnabled = false

        if (categoryId == null || categoryName == null) {
            throw IllegalStateException("categoryId and categoryName must be specified.")
        }

        getData()

        fragment_promotions_categorized_scrollview.setOnScrollChangeListener(this)
    }

    private fun getData() {
        Http.get("get/promotion/$categoryId").responseObject<List<PromotionResponse>> { _, response, result ->
            if (response.isSuccessful) {
                setUpRecyclerViewUi(result.get())
            }
        }
    }

    @SuppressLint("SetTextI18n")
    private fun setUpRecyclerViewUi(data: List<PromotionResponse>) {
        promotionController.setData(data) // The UI won't update

        fragment_promotions_categorized_header.text = "$categoryName Promotions"
        fragment_promotions_categorized_number_result.text = "Found ${data.size} Promotions"
    }

    override fun onScrollChange(v: NestedScrollView?, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) {
        if (scrollY == (v?.getChildAt(0)?.measuredHeight?.minus(v.measuredHeight))) {
            promotionController.loadMore()
        }
    }

PromotionController.kt

class PromotionController : EpoxyController() {
    private var promotionsBuffer: List<PromotionResponse> = listOf()
    private var allPromotions: List<PromotionResponse> = listOf()

    private var currentMaxSize: Int = 5

    override fun buildModels() {
        if (currentMaxSize > allPromotions.size) {
            currentMaxSize = allPromotions.size
        }

        if (promotionsBuffer.size == allPromotions.size) {
            return
        }

        promotionsBuffer = allPromotions.take(currentMaxSize)

        Log.d("PromotionController", "allPromotions Size: ${allPromotions.size}")
        Log.d("PromotionController", "promotionsBuffer Size: ${promotionsBuffer.size}")
        
        // The UI won't update
        for (promotion in promotionsBuffer) {
            popPromo {
                id(promotion.Id)
                promoName(promotion.promotion_name)
                promoDesc(promotion.promotion_detail)
                promoExp(promotion.promotion_end_date)
                promoPoint(promotion.reward?.get(0)?.reward_point_cost ?: 0)
                promoPicUrl(promotion.image?.get(0)?.ImageName ?: "")
                promoStoreName(promotion.company_name)
            }
        }
    }

    fun setData(data: List<PromotionResponse>) {
        allPromotions = data
        currentMaxSize = 5
        requestModelBuild()
    }

    fun loadMore() {
        Log.d("PromotionController", "Loading More.. BRRRRRRRRR")
        currentMaxSize += 5
        requestModelBuild()
    }
}

And the model:

@EpoxyModelClass
abstract class PopPromoModel : EpoxyModelWithHolder<PopPromoModel.PopPromoViewHolder>() {
    @EpoxyAttribute
    var promoPicUrl: String = ""

    @EpoxyAttribute
    var promoPoint: Int = 0

    @EpoxyAttribute
    var promoName: String = ""

    @EpoxyAttribute
    var promoStoreName: String = ""

    @EpoxyAttribute
    var promoDesc: String = ""

    @EpoxyAttribute
    var promoExp: String = ""

    @JvmField
    @EpoxyAttribute
    var isBig: Boolean = true

    override fun getDefaultLayout(): Int {
        return R.layout.model_pop_promo
    }

    @SuppressLint("SetTextI18n")
    override fun bind(holder: PopPromoViewHolder) {
        Picasso.get()
                .load("${FuelManager.instance.basePath}/Images/$promoPicUrl")
                .fit()
                .centerCrop()
                .placeholder(R.drawable.placeholder)
                .error(R.drawable.placeholder)
                .into(holder.promoPic)

        holder.promoPoint.text = promoPoint.toString()
        holder.promoName.text = promoName
        holder.promoDesc.text = promoDesc
        holder.promoStoreName.text = promoStoreName
        holder.promoExp.text = "Valid Until: ${promoExp.formatDbDate("d MMM yyyy")}"

        if (!isBig) {
            holder.pointContainer.visibility = View.GONE

            val pointText = "$promoPoint Point"
            val spannablePointText = SpannableString(pointText)

            spannablePointText.setSpan(
                    ForegroundColorSpan(Color.rgb(170, 170, 170)),
                    pointText.indexOf("Point"), spannablePointText.length,
                    Spannable.SPAN_INCLUSIVE_INCLUSIVE
            )

            spannablePointText.setSpan(
                    StyleSpan(Typeface.BOLD),
                    pointText.indexOf(promoPoint.toString()), promoPoint.toString().length,
                    Spannable.SPAN_INCLUSIVE_INCLUSIVE
            )

            holder.promoPointSmall.text = spannablePointText
            holder.promoPointSmall.visibility = View.VISIBLE
        }
    }

    inner class PopPromoViewHolder : KotlinHolder() {
        val promoPic        by bind<RoundedImageView>(R.id.model_pop_promo_pic)
        val promoPoint      by bind<TextView>(R.id.model_pop_promo_point_text)
        val promoName       by bind<TextView>(R.id.model_pop_team_epic) // yes
        val promoDesc       by bind<TextView>(R.id.model_pop_promo_desc)
        val promoStoreName  by bind<TextView>(R.id.model_pop_promo_store_text)
        val promoExp        by bind<TextView>(R.id.model_pop_promo_exp)

        val pointContainer  by bind<LinearLayout>(R.id.model_pop_promo_point_right_container)
        val promoPointSmall by bind<TextView>(R.id.model_pop_promo_point_text_small)
    }
}

However, it gets weirder.

If I set controller to recyclerview after the data has been loaded from API like this:

private fun setUpRecyclerViewUi(data: List<PromotionResponse>) {
    fragment_promotions_categorized_recycler_view.setController(promotionController)
    fragment_promotions_categorized_recycler_view.isNestedScrollingEnabled = false

    promotionController.setData(data)

    fragment_promotions_categorized_header.text = "$categoryName Promotions"
    fragment_promotions_categorized_number_result.text = "Found ${data.size} Promotions"
}

The UI will update with initial 5 rows as expected. But, if the user scroll to bottom of the nestedScrollView, the fragment will call loadMore() method inside PromotionController, which will then call requestModelBuild(). But, the UI won’t update with newly appended data.

Any idea what might be causing this?

Also note that PopPromoModel has been used with other fragment / controller and it works perfectly, so I’m quite sure that the model is not what causing this problem.

UPDATE 0: Ok, I tried logging out the itemCount from recyclerView’s adapter. It is clear the item count keep increasing as expected, but the UI itself won’t update.

UPDATE 1: I also logged out models using interceptor, and as expected, it also has all the data as expected, but, again, the UI won’t update.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:6 (1 by maintainers)

github_iconTop GitHub Comments

5reactions
premacckcommented, Dec 21, 2018

I was facing the same issue, where I had vertical RecyclerView with each item containing optional vertical RecyclerViews. Whenever an item in the inner RecyclerView was being added, the list wasn’t being updated. And as it turns out, I too, was passing the object’s "id" field in the id() method.

So what I did was passed object.toString() instead of object.id (in your case, it would be replacing promotion.Id with promotion.toString() in the id() method) and voila! It started updating the views.

1reaction
elihartcommented, Dec 22, 2018

I wouldn’t recommend using object.toString() as the model id - that is likely a slow operation and inefficient when building models.

It sounds like your problem is that you have duplicate ids that aren’t unique, that should be addressed instead.

Generally I would recommend using EpoxyController’s setFilterDuplicates and setGlobalExceptionHandler or onExceptionSwallowed to be alerted to these issues more clearly

Read more comments on GitHub >

github_iconTop Results From Across the Web

RecyclerView doesn't update the UI after data changed
Try using adapter.notifyDataSetChanged () , if that does not work, then your problem is in your data. If it works, then check if...
Read more >
Calling NotifyAll to Update RecyclerView from External Method
I have a RecyclerView (with an Adapter, ViewHolder, etc.), and I need to update the data (the IEnumerable passed to the Adapter).
Read more >
How to Update RecycleView Adapter Data in Android?
For updating data in the RecyclerView, we have to use the Adapter class. Adapter handles all the data within our recycler view.
Read more >
Create dynamic lists with RecyclerView - Android Developers
When the view holder is created, it doesn't have any data associated with it. After the view holder is created, the RecyclerView binds...
Read more >
Kotlin RecyclerView not updating after data changes-kotlin
Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found