RecyclerView won't update view after data change.
See original GitHub issueHi,
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:
- Created 5 years ago
- Comments:6 (1 by maintainers)
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 theid()
method.So what I did was passed
object.toString()
instead ofobject.id
(in your case, it would be replacingpromotion.Id
withpromotion.toString()
in theid()
method) and voila! It started updating the views.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
andsetGlobalExceptionHandler
oronExceptionSwallowed
to be alerted to these issues more clearly