diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channelconfig/internal/DatabaseChannelConfigRepository.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channelconfig/internal/DatabaseChannelConfigRepository.kt index d3b7708637b..6f5a34acbdd 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channelconfig/internal/DatabaseChannelConfigRepository.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channelconfig/internal/DatabaseChannelConfigRepository.kt @@ -48,11 +48,15 @@ internal class DatabaseChannelConfigRepository( * Writes many [ChannelConfig] */ override suspend fun insertChannelConfigs(configs: Collection) { + // Channel configs are keyed by type, so a page of same-type channels yields many configs + // that collapse to one row. Dedup by type to avoid the redundant per-row writes. + val configsByType = configs.associateBy(ChannelConfig::type) + // update the local configs - channelConfigs += configs.associateBy(ChannelConfig::type) + channelConfigs += configsByType // insert into room db - channelConfigDao.insert(configs.map(ChannelConfig::toEntity)) + channelConfigDao.insert(configsByType.values.map(ChannelConfig::toEntity)) } /** diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/user/internal/DatabaseUserRepository.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/user/internal/DatabaseUserRepository.kt index c64b936404f..73940fd2700 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/user/internal/DatabaseUserRepository.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/user/internal/DatabaseUserRepository.kt @@ -58,12 +58,16 @@ internal class DatabaseUserRepository( */ override suspend fun insertUsers(users: Collection) { if (users.isEmpty()) return - val usersToInsert = users + // Use associateBy instead of distinctBy to keep the *last* occurrence of each user + val uniqueUsers = users.associateBy(User::id).values + val usersToInsert = uniqueUsers .filter { it != userCache[it.id] } .map { it.toEntity() } - cacheUsers(users) + cacheUsers(uniqueUsers) scope.launchWithMutex(dbMutex) { - logger.v { "[insertUsers] inserting ${usersToInsert.size} entities on DB, updated ${users.size} on cache" } + logger.v { + "[insertUsers] inserting ${usersToInsert.size} entities in DB, updated ${uniqueUsers.size} in cache" + } usersToInsert .takeUnless { it.isEmpty() } ?.let { userDao.insertMany(it) } diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/ChannelConfigRepositoryTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/ChannelConfigRepositoryTest.kt index 00bb22f66db..cbde874c516 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/ChannelConfigRepositoryTest.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/ChannelConfigRepositoryTest.kt @@ -97,6 +97,25 @@ internal class ChannelConfigRepositoryTest { ) } + @Test + fun `When insert configs with duplicate types Should dedup keeping the last per type`() = runTest { + val first = randomChannelConfig(type = "messaging", config = randomConfig(name = "first")) + val last = randomChannelConfig(type = "messaging", config = randomConfig(name = "last")) + val other = randomChannelConfig(type = "livestream", config = randomConfig(name = "other")) + + sut.insertChannelConfigs(listOf(first, other, last)) + + verify(dao).insert( + argThat> { + size == 2 && + single { it.channelConfigInnerEntity.channelType == "messaging" } + .channelConfigInnerEntity.name == "last" && + single { it.channelConfigInnerEntity.channelType == "livestream" } + .channelConfigInnerEntity.name == "other" + }, + ) + } + @Test fun `Given config in cache When select Should return config`() = runTest { val config = randomChannelConfig(type = "messaging", config = randomConfig(name = "configName")) diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/UserRepositoryTests.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/UserRepositoryTests.kt index 01e590bccae..5920bb7403d 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/UserRepositoryTests.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/offline/repository/UserRepositoryTests.kt @@ -51,6 +51,7 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times @@ -131,6 +132,21 @@ internal class UserRepositoryTests { verify(userDao, never()).insertMany(any()) } + @Test + fun `When insert users with duplicate ids Should insert each user only once keeping the last`() = runTest { + val id = randomString() + val firstCopy = randomUser(id = id, name = "first") + val lastCopy = firstCopy.copy(name = "last") + val other = randomUser() + + sut.insertUsers(listOf(firstCopy, other, lastCopy)) + + val captor = argumentCaptor>() + verify(userDao).insertMany(captor.capture()) + captor.firstValue.map(UserEntity::id) shouldBeEqualTo listOf(id, other.id) + captor.firstValue.first { it.id == id }.name shouldBeEqualTo "last" + } + @Test fun `When insert current user Should insert entity with me id to dao`() = runTest { val user = randomUser(