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.
UPDATE 2: Yep, I got it working by swapping out EpoxyRecyclerView with normal RecyclerView. I think the main issue is that EpoxyRecyclerView somehow did not get notify about data change. I'm gonna change the issue name for now.
This is what my working code looks like.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (categoryId == null || categoryName == null) {
throw IllegalStateException("categoryId and categoryName must be specified.")
}
fragment_promotions_categorized_recycler_view.layoutManager = LinearLayoutManager(context)
fragment_promotions_categorized_recycler_view.adapter = promotionController.adapter
fragment_promotions_categorized_recycler_view.isNestedScrollingEnabled = false
fragment_promotions_categorized_scrollview.setOnScrollChangeListener(this)
getData()
}
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)
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()
}
}
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.
Thank you! It's working as intended now!
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
Will do. Thanks a bunch.
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
setFilterDuplicatesandsetGlobalExceptionHandleroronExceptionSwallowedto be alerted to these issues more clearly
Hi, is there any other workaround besides the things you mentioned? I need to update the title of items inside a horizontal recyclerview (the id-s stay the same, its just the titles that need to change - to be more precise I rely on the id-s because they are hard coded). When I update the titles just the ones that are not visible are being updated, when I scroll the vertical list eventually all items change the title. When I set the title as the id it of the model itworks as intended. Is there some workaround to force epoxy to redraw the horizontal recyclerview in these partical situations when I change the titles?
Thank you
Most helpful comment
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.Idwithpromotion.toString()in theid()method) and voila! It started updating the views.