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.

[Question] Update multiple views separately by Click Listener

See original GitHub issue

Hello, I’m new to the library and I have an issue while implementing my example app. In general, I have a list view of model (using @EpoxyModelClass and @EpoxyAttribute manually). In each model, I have a button download and download_progress_view (updated by progress). When clicking on download button, then it will update progress of view in model with different values. I have problem with this, click on download then all model update progress instead of the clicked one. Do you have any suggestions?

Thanks for great library. Please have a look at relevant code below:

My Model

@EpoxyModelClass(layout = R.layout.view_holder_course_detail_chapter_item)
abstract class CourseDetailChapterModel : EpoxyModelWithHolder<CourseChapterViewHolder>() {

    @EpoxyAttribute
    lateinit var onClickListener: () -> Unit
    @EpoxyAttribute
    lateinit var courseChapter: CourseChapter
    @EpoxyAttribute
    lateinit var course: Course
    @EpoxyAttribute
    lateinit var purchaseState: PurchaseState
    @EpoxyAttribute
    lateinit var authState: AuthState
    @EpoxyAttribute
    lateinit var context: Context
    @EpoxyAttribute
    lateinit var onDownloadClick: () -> Unit
    @EpoxyAttribute
    var myProgress: Float = 0F

    @SuppressLint("SetTextI18n")
    override fun bind(holder: CourseChapterViewHolder) {
        var fileName = "${courseChapter.courseId}:${courseChapter.chapterNumber}:${courseChapter.version}.pdf"
        var fileExisted = FileUtils.checkFileExisted(context, fileName)

        holder.chapterDownloadProgress.color = Color.parseColor("#${course.colorPrimary}")
        holder.chapterTitleTextView.text = "${courseChapter.chapterNumber}. ${courseChapter.title}"
        if (courseChapter.currentlyPlaying) {
            holder.chapterTitleTextView.setTextColor((Color.parseColor("#${course.colorPrimary}")))
        } else {
            holder.chapterTitleTextView.setTextColor(Color.WHITE)
        }
        holder.chapterDurationTextView.text = millisecondsToString(courseChapter.duration)
        holder.chapterBackground.setOnClickListener { onClickListener.invoke() }
        holder.chapterDescriptionTextView.text = courseChapter.description
        if (courseChapter.isTrial || (purchaseState.purchased && authState.authed)) {
            holder.chapterLockIcon.visibility = View.GONE
        }

        if (fileExisted) {
            holder.chapterDownloadTextView.visibility = VISIBLE
            holder.chapterDownloadedIcon.visibility = VISIBLE
            holder.chapterDownloadTextView.text = context.getString(R.string.lesson_downloaded)
            holder.chapterDownloadProgress.progress = 0f
            holder.chapterDownloadTextView.setOnClickListener(null)
        } else {
                holder.chapterDownloadedIcon.visibility = GONE
                holder.chapterDownloadTextView.visibility = VISIBLE
                holder.chapterDownloadTextView.text = context.getString(R.string.lesson_download)
                holder.chapterDownloadTextView.setOnClickListener { onDownloadClick.invoke() }

                holder.chapterDownloadProgress.progress = myProgress
                if (myProgress > 0F) {
                    holder.chapterDownloadProgress.visibility = VISIBLE
                    holder.chapterDownloadTextView.visibility = GONE
                    holder.chapterDownloadedIcon.visibility = GONE

                    if (myProgress in 99F..100F) {
                        holder.chapterDownloadProgress.visibility = GONE
                        holder.chapterDownloadTextView.text = context.getString(R.string.lesson_downloaded)
                        holder.chapterDownloadTextView.visibility = VISIBLE
                        holder.chapterDownloadedIcon.visibility = VISIBLE
                    }
                }
        }
    }

    override fun unbind(holder: CourseChapterViewHolder) {
        super.unbind(holder)
        holder.chapterLockIcon.visibility = View.VISIBLE
        holder.chapterTitleTextView.setTextColor(Color.WHITE)
    }

    private fun millisecondsToString(millis: Long): String {
        val hours = millis / 60
        val minutes = millis % 60
        return if (hours > 0) {
            "$hours hr $minutes min"
        } else {
            "$minutes min"
        }
    }
}

class CourseChapterViewHolder : BaseEpoxyHolder() {

    val chapterBackground by bind<View>(R.id.course_chapter_item_background)
    val chapterTitleTextView by bind<TextView>(R.id.course_chapter_item_title_text_view)
    val chapterDurationTextView by bind<TextView>(R.id.course_chapter_item_duration_text_view)
    val chapterDescriptionTextView by bind<TextView>(R.id.course_chapter_description_text_view)
    val chapterLockIcon by bind<ImageView>(R.id.course_chapter_item_lock_image_view)
    val chapterDownloadTextView by bind<TextView>(R.id.download_title)
    val chapterDownloadedIcon by bind<ImageView>(R.id.downloaded_icon)
    val chapterDownloadProgress by bind<CircularProgressBar>(R.id.download_status)

}

My controller

class CourseDetailChaptersController(private val course: Course, private val context: Context) : Typed4EpoxyController<Boolean, List<CourseChapter>, Boolean, Boolean>() {

    var chaptersListener: CourseDetailChaptersListener? = null

    var downloadListener: CourseDetailChaptersDownloadListener? = null

    private var myProgress: Float = 0F

    fun setProgress(progress: Float) {
        this.myProgress = progress
    }

    override fun buildModels(loading: Boolean, chapters: List<CourseChapter>, coursePurchased: Boolean, authenticated: Boolean) {
        if (loading) {
            courseDetailLoading {
                id("loading")
            }
            return
        }
        if (chapters.isNotEmpty()) {
            courseDetailEmptySpace {
                id("empty_space")
            }
            for (chapter in chapters.sortedBy { it.chapterNumber }) {
                courseDetailChapter {
                    context(context)
                    id(chapter.id)
                    courseChapter(chapter)
                    purchaseState(PurchaseState(coursePurchased))
                    course(course)
                    authState(AuthState(authenticated))
                    onClickListener{
                        chaptersListener?.onChapterClicked(chapter)
                    }
                    onDownloadClick {
                        downloadListener?.onDownloadClicked(chapter)
                    }
                    myProgress(myProgress)
                }
            }
        }
    }

}

Handle onDownloadClicked

private fun downloadFile(chapter: CourseChapter, downloadUrl: String) {
        val myDirectory = File(context!!.filesDir.path + "/MyResources/")
        var filePath: String
        Fuel.download(downloadUrl)
                .fileDestination { response, url ->
                    myDirectory.mkdir()
                    filePath = File(myDirectory.absolutePath, "${chapter.courseId}:${chapter.chapterNumber}:${chapter.version}.pdf").absolutePath
                    File(filePath)
                }
                .progress { readBytes, totalBytes ->
                    val progress = readBytes.toFloat() / totalBytes.toFloat() * 100
                    Timber.i("Bytes downloaded $readBytes / $totalBytes ($progress %)")
                    controller.setProgress(progress = progress)
                    handleDataState(chaptersViewModel?.getChapters()!!.value!!)
                }
                .response { result ->
                    Timber.i(result.toString())
                    if (result.toString().contains("Success")) {
                        activity!!.runOnUiThread {
                            Toast.makeText(context, "Downloaded file ${chapter.courseId}:${chapter.chapterNumber}:${chapter.version}.pdf", Toast.LENGTH_LONG).show()
                        }
                    }
                }
    }

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
elihartcommented, May 17, 2019

Oh, I see what you are saying - this is because your data structure isn’t set up properly - you are using the same progress value for all models.

Epoxy always rebuilds all models - get in the mindset of thinking of your entire data set mapping to the models, and all models are rebuilt from the data.

you need to change your data structure to completely represent your models. The MvRx library is good for that and works very nicely with epoxy, but you can also just use a data class. You shouldn’t be using the typed adapter - it’s messy when you have 4 parameters, plus you are setting data via the progress setter.

anyway, for your specific problem, you need a map of chapter to progress

val progressMap = mutableMapOf<Chapter, Float>()
fun setProgress(chapter: Chapter, progress: Float) = progressMap[chapter] = progress

and when you build models, grab the right progress for the chapter

0reactions
longnguyencmgcommented, May 17, 2019

Thank you very much for your solution. It works well now. Cheers 😃

Read more comments on GitHub >

github_iconTop Results From Across the Web

Multiple click listeners on buttons - java - Stack Overflow
I want to know how to add multiple click events to buttons defined in XML, as previously, in Java, we implemented View.onClickListener ......
Read more >
Introduction to events - Learn web development | MDN
Inside the addEventListener() function, we specify two parameters: the name of the event we want to register this handler for, and the code...
Read more >
Multi-window support - Android Developers
Multi -window mode enables multiple apps to share the same screen simultaneously. Apps can be side by side or one above the other...
Read more >
API Reference | Eventbrite Platform
Eventbrite has many models that refer to each other, and often you'll want to fetch related data along with the primary model you're...
Read more >
Updates to a BIND dynamic zone that is shared between ...
Different views act separately, it's essentially a convenience over running separate instances of named. If there are zones with the same name in...
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