Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.getstream.feeds.android.client.internal.state

import io.getstream.android.core.api.sort.CompositeComparator
import io.getstream.android.core.api.sort.Sort
import io.getstream.feeds.android.client.api.model.FeedMemberData
import io.getstream.feeds.android.client.api.model.ModelUpdates
Expand All @@ -25,7 +26,7 @@ import io.getstream.feeds.android.client.api.state.query.MembersQuery
import io.getstream.feeds.android.client.api.state.query.MembersQueryConfig
import io.getstream.feeds.android.client.api.state.query.MembersSort
import io.getstream.feeds.android.client.internal.utils.mergeSorted
import io.getstream.feeds.android.client.internal.utils.upsert
import io.getstream.feeds.android.client.internal.utils.upsertSorted
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand Down Expand Up @@ -69,7 +70,9 @@ internal class MemberListStateImpl(override val query: MembersQuery) : MemberLis
}

override fun onMemberAdded(member: FeedMemberData) {
_members.update { current -> current.upsert(member, FeedMemberData::id) }
_members.update { current ->
current.upsertSorted(member, FeedMemberData::id, membersSorting)
}
}

override fun onMemberRemoved(memberId: String) {
Expand All @@ -78,13 +81,7 @@ internal class MemberListStateImpl(override val query: MembersQuery) : MemberLis

override fun onMemberUpdated(member: FeedMemberData) {
_members.update { current ->
current.map {
if (it.id == member.id) {
member
} else {
it
}
}
current.upsertSorted(member, FeedMemberData::id, membersSorting)
}
}

Expand All @@ -93,14 +90,19 @@ internal class MemberListStateImpl(override val query: MembersQuery) : MemberLis
val updatesMap = updates.updated.associateBy(FeedMemberData::id)

_members.update { current ->
// Apply updates and filter out removed members in a single pass
current.mapNotNull { member ->
if (member.id in updates.removedIds) {
null
} else {
updatesMap[member.id] ?: member
current
.mapNotNullTo(mutableListOf()) { member ->
// Apply updates and filter out removed members in a single pass
if (member.id in updates.removedIds) {
null
} else {
updatesMap[member.id] ?: member
}
}
.apply {
addAll(updates.added)
sortWith(CompositeComparator(membersSorting))
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ package io.getstream.feeds.android.client.internal.state
import io.getstream.feeds.android.client.api.model.FeedId
import io.getstream.feeds.android.client.api.model.FeedMemberData
import io.getstream.feeds.android.client.api.model.ModelUpdates
import io.getstream.feeds.android.client.api.model.PaginationData
import io.getstream.feeds.android.client.api.model.PaginationResult
import io.getstream.feeds.android.client.api.state.query.MembersQuery
import io.getstream.feeds.android.client.api.state.query.MembersQueryConfig
import io.getstream.feeds.android.client.api.state.query.MembersSort
import io.getstream.feeds.android.client.internal.test.TestData.defaultPaginationResult
import io.getstream.feeds.android.client.internal.test.TestData.feedMemberData
import java.util.Date
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
Expand Down Expand Up @@ -53,24 +52,27 @@ internal class MemberListStateImplTest {
}

@Test
fun `on memberUpdated, then update specific member`() = runTest {
val initialMembers = listOf(feedMemberData(), feedMemberData("user-2"))
val paginationResult = defaultPaginationResult(initialMembers)
memberListState.onQueryMoreMembers(paginationResult, queryConfig)
fun `on memberUpdated, then update and reposition member`() = runTest {
// Initial members already sorted by createdAt desc
val initialMembers =
listOf(
feedMemberData("user-2", createdAt = Date(2000)),
feedMemberData("user-1", createdAt = Date(1000)),
)
setupInitialState(initialMembers)

val updatedMember = feedMemberData("user-1", role = "admin")
val updatedMember = feedMemberData("user-1", role = "admin", createdAt = Date(3000))
memberListState.onMemberUpdated(updatedMember)

val updatedMembers = memberListState.members.value
assertEquals(updatedMember, updatedMembers.find { it.id == updatedMember.id })
assertEquals(initialMembers[1], updatedMembers.find { it.id == initialMembers[1].id })
// Member should be repositioned according to new sort criteria
val expectedMembers = listOf(updatedMember, initialMembers[0])
assertEquals(expectedMembers, memberListState.members.value)
}

@Test
fun `on memberRemoved, then remove specific member`() = runTest {
val initialMembers = listOf(feedMemberData(), feedMemberData("user-2"))
val paginationResult = defaultPaginationResult(initialMembers)
memberListState.onQueryMoreMembers(paginationResult, queryConfig)
setupInitialState(initialMembers)

memberListState.onMemberRemoved(initialMembers[0].id)

Expand All @@ -79,63 +81,80 @@ internal class MemberListStateImplTest {
}

@Test
fun `on membersUpdated, then apply multiple updates`() = runTest {
val initialMembers = listOf(feedMemberData(), feedMemberData("user-2"))
val paginationResult = defaultPaginationResult(initialMembers)
memberListState.onQueryMoreMembers(paginationResult, queryConfig)
fun `on membersUpdated, then apply add, update, and remove operations`() = runTest {
val initialMembers =
listOf(
feedMemberData("user-3", createdAt = Date(3000)),
feedMemberData("user-2", createdAt = Date(2000)),
feedMemberData("user-1", createdAt = Date(1000)),
)
setupInitialState(initialMembers)

val updatedMember = feedMemberData("user-1", role = "admin")
val updatedMember = feedMemberData("user-1", role = "admin", createdAt = Date(5000))
val newMember = feedMemberData("user-4", createdAt = Date(4000))
val updates =
ModelUpdates(
added = emptyList(),
added = listOf(newMember),
updated = listOf(updatedMember),
removedIds = listOf(initialMembers[1].id),
removedIds = listOf("user-2"),
)
memberListState.onMembersUpdated(updates)

val finalMembers = memberListState.members.value
assertEquals(listOf(updatedMember), finalMembers)
// Members should be sorted by createdAt in descending order
val expectedMembers = listOf(updatedMember, newMember, initialMembers[0])
assertEquals(expectedMembers, memberListState.members.value)
}

@Test
fun `on memberAdded, then add member`() = runTest {
val initialMembers = listOf(feedMemberData(), feedMemberData("user-2"))
val paginationResult =
PaginationResult(models = initialMembers, pagination = PaginationData())
memberListState.onQueryMoreMembers(paginationResult, queryConfig)
fun `on memberAdded, then add member in sorted position`() = runTest {
// Initial members already sorted by createdAt desc
val initialMembers =
listOf(
feedMemberData("user-3", createdAt = Date(3000)),
feedMemberData("user-1", createdAt = Date(1000)),
)
setupInitialState(initialMembers)

val newMember = feedMemberData("user-3")
val newMember = feedMemberData("user-2", createdAt = Date(2000))
memberListState.onMemberAdded(newMember)

assertEquals(initialMembers + newMember, memberListState.members.value)
// Member should be inserted in correct sorted position
val expectedMembers = listOf(initialMembers[0], newMember, initialMembers[1])
assertEquals(expectedMembers, memberListState.members.value)
}

@Test
fun `on memberAdded with existing id, then update member`() = runTest {
val initialMembers = listOf(feedMemberData(), feedMemberData("user-2"))
val paginationResult = defaultPaginationResult(initialMembers)
memberListState.onQueryMoreMembers(paginationResult, queryConfig)
fun `on memberAdded with existing id, then update and reposition member`() = runTest {
// Initial members already sorted by createdAt desc
val initialMembers =
listOf(
feedMemberData("user-2", createdAt = Date(2000)),
feedMemberData("user-1", createdAt = Date(1000)),
)
setupInitialState(initialMembers)

val updatedMember = feedMemberData("user-1", role = "admin")
// Add existing user-1 with newer createdAt that should move it to the front
val updatedMember = feedMemberData("user-1", role = "admin", createdAt = Date(3000))
memberListState.onMemberAdded(updatedMember)

val updatedMembers = memberListState.members.value
assertEquals(2, updatedMembers.size)
assertEquals(updatedMember, updatedMembers.find { it.id == updatedMember.id })
assertEquals(initialMembers[1], updatedMembers.find { it.id == initialMembers[1].id })
// Member should be updated and repositioned according to new sort criteria (3000, 2000)
val expectedMembers = listOf(updatedMember, initialMembers[0])
assertEquals(expectedMembers, memberListState.members.value)
}

@Test
fun `on clear, then remove all members`() = runTest {
val initialMembers = listOf(feedMemberData(), feedMemberData("user-2"))
val paginationResult = defaultPaginationResult(initialMembers)
memberListState.onQueryMoreMembers(paginationResult, queryConfig)
setupInitialState(listOf(feedMemberData(), feedMemberData("user-2")))

memberListState.clear()

assertEquals(emptyList<FeedMemberData>(), memberListState.members.value)
}

private fun setupInitialState(members: List<FeedMemberData>) {
memberListState.onQueryMoreMembers(defaultPaginationResult(members), queryConfig)
}

companion object {
private val queryConfig = MembersQueryConfig(filter = null, sort = MembersSort.Default)
}
Expand Down