Search code examples
androidkotlingoogle-cloud-firestoreandroid-recyclerviewfirebaseui

How to provide conditional visibility of a child-item in a RecyclerView in the Adapter class?


I am working on a project which implements 2 views for each screen, a normal user view, and an admin view. The admin view is presented with a little more privileges than a normal user like deleting certain posts or the users themselves from the database.

Therefore, I set the visibility of those functional buttons to be GONE if the admin privilege is true (which I pass as a parameter value when initializing the adapter). But what I am struggling with, is where do I set the visibility, in the onCreateViewHolder method or onBindViewHolder method? I have right now set it in the onCreateViewHolder method because I had read on some Stackoverflow answer only that we should avoid heavy operations in onBindViewHolder method. But I would like to know a definitive answer.

Here are the code samples for reference:

The adapter class declaration:

class NoticesAdapter(options: FirestoreRecyclerOptions<NoticeModel>,
                    private val isAdmin: Boolean,
                    private val listener: INoticeListAdapter):
        FirestoreRecyclerAdapter<NoticeModel, NoticesAdapter.NoticeViewHolder>(options)

onCreateViewHolder meothod:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoticeViewHolder {

        val noticeListView = LayoutInflater.from(parent.context).inflate(R.layout.item_notice, parent, false)
        val noticeListViewHolder = NoticeViewHolder(noticeListView)

        if (!isAdmin)
        {
            noticeListViewHolder.deleteNoticeBtn.visibility = GONE
        }

        // On clicking the delete button on a notice by the admin
        noticeListViewHolder.deleteNoticeBtn.setOnClickListener {
            val noticeSnapshot = snapshots.getSnapshot(noticeListViewHolder.adapterPosition)
            listener.deleteNoticeBtnListener(noticeSnapshot)
        }

        return noticeListViewHolder
    }

The onBindViewHolder method:

override fun onBindViewHolder(holder: NoticeViewHolder, position: Int, model: NoticeModel) {
        holder.noticeText.text = model.noticeText
        holder.noticeAuthor.text = MyUtils.getUserName()
        holder.noticePostDate.text = model.datePosted
        holder.noticePostTime.text = model.timePosted

    }

Solution

  • A RecyclerView.Adapter what it does is to: recycle items (as the name implies). The list doesn't have one view per item on the data source at the same time. The adapter makes sure to have enough views in memory in order to always render the list smoothly. When a row is leaving the field of view by scrolling, then that view is recycled to be re-used in the next entering view to the screen size.

    This means that onCreateViewHolder is called only when a view is needed to be created. Generally at the start of the adapter, also when the user is scrolling fast or erratically and when the data set changes and is needed.

    The other method onBindViewHolder is called every time the data on the row needs to be updated in order for the view to get updated. This is called every time a row is entering the view field of the screen.

    So the textbook answer is: do it on onBindViewHodlder, because if the attribute isAdmin changes then that row will need to be updated. By doing it on onCreateViewHolder that would only happen one time when the row is created.

    But, your isAdmin is a val on the constructor that can not be reassigned, so this means that when the rows are created the button will be hidden or visible forever. And this doesn't matter because your structure is to determine if is admin from another source that is separated from which the row data structure is derived from.

    If in some case you want to:

    • make it more flexible and easier to maintain in the future
    • or maybe you know there is going to be a case where there is gonna be a list with admins and not admins rows

    Then the solution is to move the isAdming attribute to your NoticeModel, that would imply changing your data structure.

    If you want to verify anything sai above, get a data source with plenty of items and then add 2 logs, one on onCreateViewHolder and one in onBindViewHolder. You will see how on create is called only sometimes but on bind is called always.