1

I was implementing viewHolderScope for collecting flows in RecyclerView.ViewHolders. It would start from onBindViewHolder and gets cancelled onRecycleViewHolder. But along with this requirement, it should also automatically get cancelled when the surrounding lifecycleScope ends in case of Activity or surrounding viewLifecycleScope ends in case of Fragment. So, how can I inherit from those scopes?

interface ScopeProvider {
    val scope: CoroutineScope
}

val viewHolderScope = CoroutineScope(ScopeProvider.scope.coroutineContext)

Does this work as intended?

Edit: Since many are asking why I am collecting flows in ViewHolder, here I will tell what I was trying to do and why I choose to do that.

I was building a chat application. In that, consider the chats list screen. There I wanted to show if a user is active or not with a green dot (like facebook). For this, if I have to collect flows in ViewModel, then I think I have to collect flows for all the users in the chats list and not just the ones visible on screen. So I thought instead I could collect flows in ViewHolder itself starting onBind and ending short after onRecycle. Anyway it had a defined lifecycle, and so I thought a viewHolderScope can be implemented.

Edit2: Entire flow of user status.

<ViewHolder>
viewHolderScope.launch {
    chatStatusObserver.statusFlow(getItem(bindingAdapterPosition)!!).collect {
        // update view
    }
}

<Fragment>
private val chatStatusObserver = object : ChatStatusObserver {

    override fun statusFlow(chat: Chat) = model.statusFlow(chat)
}

<ViewModel>
fun statusFlow(chat: Chat) = when (chat.type) {
    ChatType.PRIVATE -> repo.getUserStatus(chat.receiverId)
    ChatType.GROUP -> repo.getGroupStatus(chat.receiverId)
}

<Repository>
@OptIn(ExperimentalCoroutinesApi::class)
fun getUserStatus(userId: String) = callbackFlow {

    val userStatusListener = object : ValueEventListener {
        override fun onDataChange(snapshot: DataSnapshot) {
            val timestamp = snapshot.getValue<Long>()!!
            val status =
                if (timestamp == -1L) UserOnline
                else UserOffline(timestamp)
            trySend(status).onClosed {
                throw it ?: ClosedSendChannelException("Channel was closed normally")
            }
        }

        override fun onCancelled(error: DatabaseError) {
            trySend(Unavailable).onClosed {
                throw it ?: ClosedSendChannelException("Channel was closed normally")
            }
            Log.e(TAG, "onCancelled: ${error.code} : ${error.message}", error.toException())
        }
    }

    trySend(Pending).onClosed {
        throw it ?: ClosedSendChannelException("Channel was closed normally")
    }

    Firebase.database.reference.child(NODE_STATUS).child(userId)
        .addValueEventListener(userStatusListener)

    awaitClose {
        Firebase.database.reference.child(NODE_STATUS).child(userId)
            .removeEventListener(userStatusListener)
    }
}

Apart from the above flow, there is only this in ViewModel, which gets paged flow from RoomDao -> Repository and passes it to Fragment -> PagedAdapter.

val pagedChatsFlow = repo.getPagedChatsFlow().cachedIn(viewModelScope)
Sourav Kannantha B
  • 2,860
  • 1
  • 11
  • 35
  • Any special reason for collecting flow inside view holder? What functionality are you trying to build? – Arpit Shukla Nov 16 '21 at 09:07
  • 1
    I recommend you to submit already collected data to view holders (for example, from view model or presenter), you should avoid extending their responsibility – Steyrix Nov 16 '21 at 10:54
  • Its completely bad solution. As Steyrix said you should create UiModels in your VM or presenter and set them into your adapter. – rost Nov 16 '21 at 11:54
  • @ArpitShukla, I have updated my question. Please check and tell what could work in this case. – Sourav Kannantha B Nov 16 '21 at 12:37
  • @Steyrix, I have updated my question. Please check and tell what could work in this case. – Sourav Kannantha B Nov 16 '21 at 12:37
  • @rost, I have updated my question. Please check and tell what could work in this case. – Sourav Kannantha B Nov 16 '21 at 12:37
  • Can you add some code of ViewModel in the question related to this users flow? I am pretty sure there exists a better way to meet your requirements without creating a viewHolderScope. – Arpit Shukla Nov 16 '21 at 12:42
  • @ArpitShukla Done! check. – Sourav Kannantha B Nov 16 '21 at 13:05
  • In your current code, it looks like you are calling repository functions (indirectly) from your ViewHolder. This will lead to a lot of unnecessary network calls. You will be requesting status for one user multiple times. – Arpit Shukla Nov 16 '21 at 14:33
  • @ArpitShukla yeah, you are right. Can you tell me a better way please.. – Sourav Kannantha B Nov 16 '21 at 16:39
  • I can't think of an easy way here. One way can be to maintain a `Map>`. Now whenever you get a request for `Flow`, first check in the map, if the userId exists return that flow, else request repo for the flow and save it the map. – Arpit Shukla Nov 16 '21 at 17:08
  • @ArpitShukla but still, where to collect it? – Sourav Kannantha B Nov 17 '21 at 17:27
  • You should combine your room db chats flow and user status flow in the ViewModel itself to generate a flow of list of `ChatItem`. And then feed this list of `ChatItem` to the adapter. It contains all data related to chat as well as user online status. But since you have a paging flow, you will have to think about how to combine these flow together. – Arpit Shukla Nov 17 '21 at 17:30

0 Answers0