From cb3a62e89457094739b89362a58a7fb9985d732b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Mon, 17 Jul 2023 18:19:12 +0200 Subject: [PATCH] Create a `destroy()` method for our States (#4880) * Fresh instnace doesn't need to clear the state * Create a `destroy()` method on MutableGlobalState * Create a `destroy()` method into `QueryChannelsMutableState` class * Create a `destroy()` method into `ThreadMutableState` * Create a `destroy()` method into `ChannelMutableState` --- .../factory/StreamStatePluginFactory.kt | 1 - .../plugin/logic/internal/LogicRegistry.kt | 2 +- .../state/plugin/state/StateRegistry.kt | 3 + .../channel/internal/ChannelMutableState.kt | 256 ++++++++++-------- .../thread/internal/ThreadMutableState.kt | 42 +-- .../global/internal/MutableGlobalState.kt | 85 +++--- .../internal/QueryChannelsMutableState.kt | 66 +++-- .../channel/internal/ChannelStateLogicTest.kt | 6 +- 8 files changed, 256 insertions(+), 205 deletions(-) diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/factory/StreamStatePluginFactory.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/factory/StreamStatePluginFactory.kt index 04b6abfbb2e..51e20476ece 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/factory/StreamStatePluginFactory.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/factory/StreamStatePluginFactory.kt @@ -84,7 +84,6 @@ public class StreamStatePluginFactory( val chatClient = ChatClient.instance() val repositoryFacade = chatClient.repositoryFacade val clientState = chatClient.clientState - mutableGlobalState.clearState() val stateRegistry = StateRegistry( mutableGlobalState.user, diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/logic/internal/LogicRegistry.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/logic/internal/LogicRegistry.kt index e3aee396098..ea6bfbb827d 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/logic/internal/LogicRegistry.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/logic/internal/LogicRegistry.kt @@ -233,6 +233,6 @@ internal class LogicRegistry internal constructor( queryChannels.clear() channels.clear() threads.clear() - mutableGlobalState.clearState() + mutableGlobalState.destroy() } } diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/StateRegistry.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/StateRegistry.kt index 0a72186210a..19b628d8011 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/StateRegistry.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/StateRegistry.kt @@ -134,8 +134,11 @@ public class StateRegistry constructor( */ public fun clear() { job.cancelChildren() + queryChannels.forEach { it.value.destroy() } queryChannels.clear() + channels.forEach { it.value.destroy() } channels.clear() + threads.forEach { it.value.destroy() } threads.clear() } } diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/channel/internal/ChannelMutableState.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/channel/internal/ChannelMutableState.kt index 9a7fd6ef758..c415132f3de 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/channel/internal/ChannelMutableState.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/channel/internal/ChannelMutableState.kt @@ -57,31 +57,42 @@ internal class ChannelMutableState( override val cid: String = "%s:%s".format(channelType, channelId) - private val _messages = MutableStateFlow>(emptyMap()) - private val _countedMessage: MutableSet = mutableSetOf() - private val _typing = MutableStateFlow(TypingEvent(channelId, emptyList())) - private val _typingChatEvents = MutableStateFlow>(emptyMap()) - private val _rawReads = MutableStateFlow>(emptyMap()) - private val _members = MutableStateFlow>(emptyMap()) - private val _oldMessages = MutableStateFlow>(emptyMap()) - private val _watchers = MutableStateFlow>(emptyMap()) - private val _watcherCount = MutableStateFlow(0) - private val _endOfNewerMessages = MutableStateFlow(true) - private val _endOfOlderMessages = MutableStateFlow(false) - private val _loading = MutableStateFlow(false) - private val _hidden = MutableStateFlow(false) - private val _muted = MutableStateFlow(false) - private val _channelData = MutableStateFlow(null) - private val _repliedMessage = MutableStateFlow(null) - private val _quotedMessagesMap = MutableStateFlow>>(mutableMapOf()) - private val _membersCount = MutableStateFlow(0) - private val _insideSearch = MutableStateFlow(false) - private val _loadingOlderMessages = MutableStateFlow(false) - private val _loadingNewerMessages = MutableStateFlow(false) - private val _lastSentMessageDate = MutableStateFlow(null) + private var _messages: MutableStateFlow>? = MutableStateFlow(emptyMap()) + private var _countedMessage: MutableSet? = mutableSetOf() + private var _typing: MutableStateFlow? = MutableStateFlow(TypingEvent(channelId, emptyList())) + private var _typingChatEvents: MutableStateFlow>? = MutableStateFlow(emptyMap()) + private var _rawReads: MutableStateFlow>? = MutableStateFlow(emptyMap()) + private var rawReads: StateFlow> = _rawReads!! + private var _members: MutableStateFlow>? = MutableStateFlow(emptyMap()) + private var _oldMessages: MutableStateFlow>? = MutableStateFlow(emptyMap()) + private var _watchers: MutableStateFlow>? = MutableStateFlow(emptyMap()) + private var _watcherCount: MutableStateFlow? = MutableStateFlow(0) + private var _endOfNewerMessages: MutableStateFlow? = MutableStateFlow(true) + private var _endOfOlderMessages: MutableStateFlow? = MutableStateFlow(false) + private var _loading: MutableStateFlow? = MutableStateFlow(false) + private var _hidden: MutableStateFlow? = MutableStateFlow(false) + private var _muted: MutableStateFlow? = MutableStateFlow(false) + private var _channelData: MutableStateFlow? = MutableStateFlow(null) + private var _repliedMessage: MutableStateFlow? = MutableStateFlow(null) + private var _quotedMessagesMap: MutableStateFlow>>? = + MutableStateFlow(mutableMapOf()) + private var _membersCount: MutableStateFlow? = MutableStateFlow(0) + private var _insideSearch: MutableStateFlow? = MutableStateFlow(false) + private var _loadingOlderMessages: MutableStateFlow? = MutableStateFlow(false) + private var _loadingNewerMessages: MutableStateFlow? = MutableStateFlow(false) + private var _lastSentMessageDate: MutableStateFlow? = MutableStateFlow(null) /** Channel config data. */ - private val _channelConfig: MutableStateFlow = MutableStateFlow(Config()) + private var _channelConfig: MutableStateFlow? = MutableStateFlow(Config()) + + override val hidden: StateFlow = _hidden!! + override val muted: StateFlow = _muted!! + override val loading: StateFlow = _loading!! + override val loadingOlderMessages: StateFlow = _loadingOlderMessages!! + override val loadingNewerMessages: StateFlow = _loadingNewerMessages!! + override val endOfOlderMessages: StateFlow = _endOfOlderMessages!! + + override val endOfNewerMessages: StateFlow = _endOfNewerMessages!! /** the data to hide messages before */ var hideMessagesBefore: Date? = null @@ -93,15 +104,15 @@ internal class ChannelMutableState( /** The raw message list updated by recent users value. */ val messageList: StateFlow> = - _messages.combine(latestUsers) { messageMap, userMap -> messageMap.values.updateUsers(userMap) } + _messages!!.combine(latestUsers) { messageMap, userMap -> messageMap.values.updateUsers(userMap) } .stateIn(scope, SharingStarted.Eagerly, emptyList()) /** a list of messages sorted by message.createdAt */ private val sortedVisibleMessages: StateFlow> = messagesTransformation(messageList).stateIn(scope, SharingStarted.Eagerly, emptyList()) - private val _messagesState: StateFlow = - _loading.combine(sortedVisibleMessages) { loading: Boolean, messages: List -> + override val messagesState: StateFlow = + loading.combine(sortedVisibleMessages) { loading: Boolean, messages: List -> when { loading -> MessagesState.Loading messages.isEmpty() -> MessagesState.OfflineNoResults @@ -134,31 +145,30 @@ internal class ChannelMutableState( messagesMap.values.sortedBy { message -> message.createdAt ?: message.createdLocallyAt } }.stateIn(scope, SharingStarted.Eagerly, emptyList()) - override val repliedMessage: StateFlow = _repliedMessage + override val repliedMessage: StateFlow = _repliedMessage!! - override val quotedMessagesMap: StateFlow>> = _quotedMessagesMap + override val quotedMessagesMap: StateFlow>> = _quotedMessagesMap!! /** Channel config data */ - override val channelConfig: StateFlow = _channelConfig + override val channelConfig: StateFlow = _channelConfig!! override val messages: StateFlow> = sortedVisibleMessages - override val messagesState: StateFlow = _messagesState - override val oldMessages: StateFlow> = messagesTransformation(_oldMessages.map { it.values }) - override val watcherCount: StateFlow = _watcherCount + override val oldMessages: StateFlow> = messagesTransformation(_oldMessages!!.map { it.values }) + override val watcherCount: StateFlow = _watcherCount!! override val watchers: StateFlow> = - _watchers.combine(latestUsers) { watcherMap, userMap -> watcherMap.values.updateUsers(userMap) } + _watchers!!.combine(latestUsers) { watcherMap, userMap -> watcherMap.values.updateUsers(userMap) } .map { it.sortedBy(User::createdAt) } .stateIn(scope, SharingStarted.Eagerly, emptyList()) - override val typing: StateFlow = _typing + override val typing: StateFlow = _typing!! - override val reads: StateFlow> = _rawReads + override val reads: StateFlow> = rawReads .map { it.values.sortedBy(ChannelUserRead::lastRead) } .stateIn(scope, SharingStarted.Eagerly, emptyList()) - override val read: StateFlow = _rawReads + override val read: StateFlow = rawReads .combine(userFlow) { readsMap, user -> user?.id?.let { readsMap[it] } } .stateIn(scope, SharingStarted.Eagerly, null) @@ -168,15 +178,15 @@ internal class ChannelMutableState( override val unreadCount: StateFlow = read.mapLatest { it?.unreadMessages ?: 0 } .stateIn(scope, SharingStarted.Eagerly, 0) - override val members: StateFlow> = _members + override val members: StateFlow> = _members!! .combine(latestUsers) { membersMap, usersMap -> membersMap.values.updateUsers(usersMap) } .map { it.sortedBy(Member::createdAt) } .stateIn(scope, SharingStarted.Eagerly, emptyList()) - override val membersCount: StateFlow = _membersCount + override val membersCount: StateFlow = _membersCount!! override val channelData: StateFlow = - combine(_channelData.filterNotNull(), latestUsers) { channelData, users -> + combine(_channelData!!.filterNotNull(), latestUsers) { channelData, users -> if (users.containsKey(channelData.createdBy.id)) { channelData.copy(createdBy = users[channelData.createdBy.id] ?: channelData.createdBy) } else { @@ -191,20 +201,12 @@ internal class ChannelMutableState( ) ) - override val hidden: StateFlow = _hidden - override val muted: StateFlow = _muted - override val loading: StateFlow = _loading - override val loadingOlderMessages: StateFlow = _loadingOlderMessages - override val loadingNewerMessages: StateFlow = _loadingNewerMessages - override val endOfOlderMessages: StateFlow = _endOfOlderMessages - override val endOfNewerMessages: StateFlow = _endOfNewerMessages - /** If we need to recover state when connection established again. */ override var recoveryNeeded: Boolean = false - override val insideSearch: StateFlow = _insideSearch + override val insideSearch: StateFlow = _insideSearch!! - override val lastSentMessageDate: StateFlow = _lastSentMessageDate + override val lastSentMessageDate: StateFlow = _lastSentMessageDate!! override fun toChannel(): Channel { // recreate a channel object from the various observables. @@ -214,16 +216,16 @@ internal class ChannelMutableState( val cachedMessages = cachedLatestMessages.value.values.toList() val members = members.value val watchers = watchers.value - val reads = _rawReads.value.values.toList() - val watcherCount = _watcherCount.value - val insideSearch = _insideSearch.value + val reads = rawReads.value.values.toList() + val watcherCount = watcherCount.value + val insideSearch = insideSearch.value val channel = channelData .toChannel(messages, cachedMessages, members, reads, watchers, watcherCount, insideSearch) - channel.config = _channelConfig.value + channel.config = channelConfig.value channel.unreadCount = unreadCount.value - channel.hidden = _hidden.value - channel.isInsideSearch = _insideSearch.value + channel.hidden = hidden.value + channel.isInsideSearch = insideSearch channel.cachedLatestMessages = cachedLatestMessages.value.values.toList() return channel @@ -235,7 +237,7 @@ internal class ChannelMutableState( * @param isLoading Boolean. */ fun setLoadingOlderMessages(isLoading: Boolean) { - _loadingOlderMessages.value = isLoading + _loadingOlderMessages?.value = isLoading } /** @@ -244,12 +246,12 @@ internal class ChannelMutableState( * @param isLoading Boolean. */ fun setLoadingNewerMessages(isLoading: Boolean) { - _loadingNewerMessages.value = isLoading + _loadingNewerMessages?.value = isLoading } /** Sets the end for newer messages. */ fun setEndOfNewerMessages(isEnd: Boolean) { - _endOfNewerMessages.value = isEnd + _endOfNewerMessages?.value = isEnd } /** @@ -258,7 +260,7 @@ internal class ChannelMutableState( * @param isEnd Boolean */ fun setEndOfOlderMessages(isEnd: Boolean) { - _endOfOlderMessages.value = isEnd + _endOfOlderMessages?.value = isEnd } /** @@ -267,7 +269,7 @@ internal class ChannelMutableState( * @param isLoading Boolean. */ fun setLoading(isLoading: Boolean) { - _loading.value = isLoading + _loading?.value = isLoading } /** @@ -276,7 +278,7 @@ internal class ChannelMutableState( * @param isHidden Boolean */ fun setHidden(isHidden: Boolean) { - _hidden.value = isHidden + _hidden?.value = isHidden } /** @@ -285,12 +287,12 @@ internal class ChannelMutableState( * @param isMuted Boolean. */ fun setMuted(isMuted: Boolean) { - _muted.value = isMuted + _muted?.value = isMuted } /** Sets [ChannelData]. */ fun setChannelData(channelData: ChannelData) { - _channelData.value = channelData + _channelData?.value = channelData } /** @@ -299,7 +301,7 @@ internal class ChannelMutableState( * @param repliedMessage [Message] */ fun setRepliedMessage(repliedMessage: Message?) { - _repliedMessage.value = repliedMessage + _repliedMessage?.value = repliedMessage } /** @@ -308,7 +310,7 @@ internal class ChannelMutableState( * @param count Int. */ fun setMembersCount(count: Int) { - _membersCount.value = count + _membersCount?.value = count } /** Sets inside search. This must be set when a search is started in the channel and the @@ -318,16 +320,16 @@ internal class ChannelMutableState( * */ fun setInsideSearch(isInsideSearch: Boolean) { when { - isInsideSearch && !_insideSearch.value -> { + isInsideSearch && !insideSearch.value -> { cacheLatestMessages() } - !isInsideSearch && _insideSearch.value -> { + !isInsideSearch && insideSearch.value -> { cachedLatestMessages.value = emptyMap() } } - _insideSearch.value = isInsideSearch + _insideSearch?.value = isInsideSearch } /** @@ -336,7 +338,7 @@ internal class ChannelMutableState( * @param lastSentMessageDate The date of the last message. */ fun setLastSentMessageDate(lastSentMessageDate: Date?) { - _lastSentMessageDate.value = lastSentMessageDate + _lastSentMessageDate?.value = lastSentMessageDate } /** @@ -345,26 +347,26 @@ internal class ChannelMutableState( * @param channelConfig [Config] */ fun setChannelConfig(channelConfig: Config) { - _channelConfig.value = channelConfig + _channelConfig?.value = channelConfig } /** * Adds a quoted message to the state. */ fun addQuotedMessage(quotedMessageId: String, quotingMessageId: String) { - val quotesMap = _quotedMessagesMap.value - - quotesMap[quotedMessageId] = quotesMap[quotedMessageId]?.plus(quotingMessageId) ?: listOf(quotingMessageId) - - _quotedMessagesMap.value = quotesMap + _quotedMessagesMap?.apply { + val quotesMap = value + quotesMap[quotedMessageId] = quotesMap[quotedMessageId]?.plus(quotingMessageId) ?: listOf(quotingMessageId) + value = quotesMap + } } /** * Updates StateFlows related to typing updates. */ fun updateTypingEvents(eventsMap: Map, typingEvent: TypingEvent) { - _typingChatEvents.value = eventsMap - _typing.value = typingEvent + _typingChatEvents?.value = eventsMap + _typing?.value = typingEvent } /** @@ -374,7 +376,7 @@ internal class ChannelMutableState( */ fun upsertMembers(members: List) { val membersMap = members.associateBy(Member::getUserId) - _members.value = _members.value + membersMap + _members?.apply { value = value + membersMap } } /** @@ -383,7 +385,8 @@ internal class ChannelMutableState( * @param member The member to be added. */ fun addMember(member: Member) { - _membersCount.value += 1.takeUnless { _members.value.keys.contains(member.getUserId()) } ?: 0 + _membersCount?.value = membersCount.value + + (1.takeUnless { _members?.value?.keys?.contains(member.getUserId()) == true } ?: 0) upsertMembers(listOf(member)) } @@ -393,12 +396,16 @@ internal class ChannelMutableState( * @param member The member to be removed. */ fun deleteMember(member: Member) { - _membersCount.value -= _members.value.count { it.key == member.getUserId() } - _members.value = _members.value - member.getUserId() - deleteWatcher( - member.user, - _watcherCount.value - _watchers.value.count { it.key == member.getUserId() } - ) + _members?.let { + _membersCount?.value = membersCount.value - it.value.count { it.key == member.getUserId() } + it.value = it.value - member.getUserId() + } + _watchers?.let { + deleteWatcher( + member.user, + watcherCount.value - it.value.count { it.key == member.getUserId() } + ) + } } /** @@ -408,21 +415,22 @@ internal class ChannelMutableState( * @param watchersCount The current number of watchers. */ internal fun deleteWatcher(user: User, watchersCount: Int) { - _watchers.value = _watchers.value - user.id - _watcherCount.value = watchersCount.takeUnless { it < 0 } ?: _watchers.value.size + _watchers?.let { upsertWatchers((it.value - user.id).values.toList(), watchersCount) } } fun deleteMessage(message: Message, updateCount: Boolean = true) { - _messages.value = _messages.value - message.id + _messages?.apply { value = value - message.id } if (updateCount) { - _countedMessage.remove(message.id) + _countedMessage?.remove(message.id) } } fun upsertWatchers(watchers: List, watchersCount: Int) { - _watchers.value += watchers.associateBy(User::id) - _watcherCount.value = watchersCount.takeUnless { it == 0 } ?: _watchers.value.size + _watchers?.apply { + value = value + watchers.associateBy(User::id) + _watcherCount?.value = watchersCount.takeUnless { it < 0 } ?: value.size + } } /** @@ -431,17 +439,17 @@ internal class ChannelMutableState( * @param message message to be upserted. */ fun upsertMessage(message: Message, updateCount: Boolean = true) { - _messages.value = _messages.value + (message.id to message) + _messages?.apply { value = value + (message.id to message) } if (updateCount) { - _countedMessage.add(message.id) + _countedMessage?.add(message.id) } } fun upsertUserPresence(user: User) { - _members.value[user.id]?.copy(user = user)?.let { upsertMembers(listOf(it)) } - user.takeIf { _watchers.value.any { it.key == user.id } } - ?.let { upsertWatchers(listOf(it), _watcherCount.value) } + _members?.value?.get(user.id)?.copy(user = user)?.let { upsertMembers(listOf(it)) } + user.takeIf { _watchers?.value?.any { it.key == user.id } == true } + ?.let { upsertWatchers(listOf(it), watcherCount.value) } } fun increaseReadWith(message: Message) { @@ -453,7 +461,7 @@ internal class ChannelMutableState( lastMessageSeenDate = message.createdAt, ) } - _rawReads.value = _rawReads.value + (user.id to newUserRead) + _rawReads?.apply { value = value + (user.id to newUserRead) } } fun upsertReads(reads: List) { @@ -468,9 +476,11 @@ internal class ChannelMutableState( incomingUserRead.lastRead?.inOffsetWith(lastRead, OFFSET_EVENT_TIME) == true -> incomingUserRead else -> currentUserRead } - _rawReads.value = _rawReads.value + - reads.associateBy(ChannelUserRead::getUserId) + - listOfNotNull(newUserRead).associateBy(ChannelUserRead::getUserId) + _rawReads?.apply { + value = value + + reads.associateBy(ChannelUserRead::getUserId) + + listOfNotNull(newUserRead).associateBy(ChannelUserRead::getUserId) + } } /** @@ -495,19 +505,19 @@ internal class ChannelMutableState( } ?: false fun removeMessagesBefore(date: Date) { - _messages.value = _messages.value.filter { it.value.wasCreatedAfter(date) } + _messages?.apply { value = value.filter { it.value.wasCreatedAfter(date) } } } fun upsertMessages(updatedMessages: Collection, updateCount: Boolean = true) { - _messages.value += updatedMessages.associateBy(Message::id) + _messages?.apply { value += updatedMessages.associateBy(Message::id) } if (updateCount) { - _countedMessage.addAll(updatedMessages.map { it.id }) + _countedMessage?.addAll(updatedMessages.map { it.id }) } } fun setMessages(messages: List) { - _messages.value = messages.associateBy(Message::id) + _messages?.value = messages.associateBy(Message::id) } private fun cacheLatestMessages() { @@ -522,17 +532,43 @@ internal class ChannelMutableState( } fun clearCountedMessages() { - _countedMessage.clear() + _countedMessage?.clear() } fun insertCountedMessages(ids: List) { - _countedMessage.addAll(ids) + _countedMessage?.addAll(ids) + } + + fun isMessageAlreadyCounted(messageId: String): Boolean = _countedMessage?.contains(messageId) == true + + override fun getMessageById(id: String): Message? = _messages?.value?.get(id) + + internal fun destroy() { + _messages = null + _countedMessage = null + _typing = null + _typingChatEvents = null + _rawReads = null + _members = null + _oldMessages = null + _watchers = null + _watcherCount = null + _endOfNewerMessages = null + _endOfOlderMessages = null + _loading = null + _hidden = null + _muted = null + _channelData = null + _repliedMessage = null + _quotedMessagesMap = null + _membersCount = null + _insideSearch = null + _loadingOlderMessages = null + _loadingNewerMessages = null + _lastSentMessageDate = null + _channelConfig = null } - fun isMessageAlreadyCounted(messageId: String): Boolean = _countedMessage.contains(messageId) - - override fun getMessageById(id: String): Message? = _messages.value[id] - private companion object { private const val OFFSET_EVENT_TIME = 5L } diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/channel/thread/internal/ThreadMutableState.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/channel/thread/internal/ThreadMutableState.kt index 441c2e93996..7e1bd735cbb 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/channel/thread/internal/ThreadMutableState.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/channel/thread/internal/ThreadMutableState.kt @@ -29,43 +29,51 @@ internal class ThreadMutableState( override val parentId: String, scope: CoroutineScope, ) : ThreadState { - private val _messages = MutableStateFlow(emptyMap()) - private val _loading = MutableStateFlow(false) - private val _loadingOlderMessages = MutableStateFlow(false) - private val _endOfOlderMessages = MutableStateFlow(false) - private val _oldestInThread: MutableStateFlow = MutableStateFlow(null) + private var _messages: MutableStateFlow>? = MutableStateFlow(emptyMap()) + private var _loading: MutableStateFlow? = MutableStateFlow(false) + private var _loadingOlderMessages: MutableStateFlow? = MutableStateFlow(false) + private var _endOfOlderMessages: MutableStateFlow? = MutableStateFlow(false) + private var _oldestInThread: MutableStateFlow? = MutableStateFlow(null) - val rawMessage: StateFlow> = _messages - override val messages: StateFlow> = _messages + val rawMessage: StateFlow> = _messages!! + override val messages: StateFlow> = rawMessage .map { it.values } .map { threadMessages -> threadMessages.sortedBy { m -> m.createdAt ?: m.createdLocallyAt } } .stateIn(scope, SharingStarted.Eagerly, emptyList()) - override val loading: StateFlow = _loading - override val loadingOlderMessages: StateFlow = _loadingOlderMessages - override val endOfOlderMessages: StateFlow = _endOfOlderMessages - override val oldestInThread: StateFlow = _oldestInThread + override val loading: StateFlow = _loading!! + override val loadingOlderMessages: StateFlow = _loadingOlderMessages!! + override val endOfOlderMessages: StateFlow = _endOfOlderMessages!! + override val oldestInThread: StateFlow = _oldestInThread!! fun setLoading(isLoading: Boolean) { - _loading.value = isLoading + _loading?.value = isLoading } fun setLoadingOlderMessages(isLoading: Boolean) { - _loadingOlderMessages.value = isLoading + _loadingOlderMessages?.value = isLoading } fun setEndOfOlderMessages(isEnd: Boolean) { - _endOfOlderMessages.value = isEnd + _endOfOlderMessages?.value = isEnd } fun setOldestInThread(message: Message?) { - _oldestInThread.value = message + _oldestInThread?.value = message } fun deleteMessage(message: Message) { - _messages.value -= message.id + _messages?.apply { value -= message.id } } fun upsertMessages(messages: List) { - _messages.value += messages.associateBy(Message::id) + _messages?.apply { value += messages.associateBy(Message::id) } + } + + fun destroy() { + _messages = null + _loading = null + _loadingOlderMessages = null + _endOfOlderMessages = null + _oldestInThread = null } } diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/global/internal/MutableGlobalState.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/global/internal/MutableGlobalState.kt index b5600207918..8174853d153 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/global/internal/MutableGlobalState.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/global/internal/MutableGlobalState.kt @@ -30,65 +30,57 @@ import kotlinx.coroutines.flow.StateFlow */ internal class MutableGlobalState : GlobalState { - private val _totalUnreadCount = MutableStateFlow(0) - private val _channelUnreadCount = MutableStateFlow(0) - private val _banned = MutableStateFlow(false) - - private val _mutedUsers = MutableStateFlow>(emptyList()) - private val _channelMutes = MutableStateFlow>(emptyList()) - private val _typingChannels = MutableStateFlow(emptyMap()) - - private val _user = MutableStateFlow(null) - - override val totalUnreadCount: StateFlow = _totalUnreadCount - - override val channelUnreadCount: StateFlow = _channelUnreadCount - - override val muted: StateFlow> = _mutedUsers - - override val channelMutes: StateFlow> = _channelMutes - - override val banned: StateFlow = _banned - - override val typingChannels: StateFlow> = _typingChannels - - override val user: StateFlow = _user + private var _totalUnreadCount: MutableStateFlow? = MutableStateFlow(0) + private var _channelUnreadCount: MutableStateFlow? = MutableStateFlow(0) + private var _banned: MutableStateFlow? = MutableStateFlow(false) + private var _mutedUsers: MutableStateFlow>? = MutableStateFlow(emptyList()) + private var _channelMutes: MutableStateFlow>? = MutableStateFlow(emptyList()) + private var _typingChannels: MutableStateFlow>? = MutableStateFlow(emptyMap()) + private var _user: MutableStateFlow? = MutableStateFlow(null) + + override val totalUnreadCount: StateFlow = _totalUnreadCount!! + override val channelUnreadCount: StateFlow = _channelUnreadCount!! + override val muted: StateFlow> = _mutedUsers!! + override val channelMutes: StateFlow> = _channelMutes!! + override val banned: StateFlow = _banned!! + override val typingChannels: StateFlow> = _typingChannels!! + override val user: StateFlow = _user!! /** - * Clears the state of [GlobalState]. + * Destroys the state. */ - fun clearState() { - _user.value = null - _totalUnreadCount.value = 0 - _channelUnreadCount.value = 0 - _mutedUsers.value = emptyList() - _channelMutes.value = emptyList() - _banned.value = false - _typingChannels.value = emptyMap() + fun destroy() { + _user = null + _totalUnreadCount = null + _channelUnreadCount = null + _mutedUsers = null + _channelMutes = null + _banned = null + _typingChannels = null } fun setUser(user: User) { - _user.value = user + _user?.value = user } fun setTotalUnreadCount(totalUnreadCount: Int) { - _totalUnreadCount.value = totalUnreadCount + _totalUnreadCount?.value = totalUnreadCount } fun setChannelUnreadCount(channelUnreadCount: Int) { - _channelUnreadCount.value = channelUnreadCount + _channelUnreadCount?.value = channelUnreadCount } fun setBanned(banned: Boolean) { - _banned.value = banned + _banned?.value = banned } fun setChannelMutes(channelMutes: List) { - _channelMutes.value = channelMutes + _channelMutes?.value = channelMutes } fun setMutedUsers(mutedUsers: List) { - _mutedUsers.value = mutedUsers + _mutedUsers?.value = mutedUsers } /** @@ -98,13 +90,16 @@ internal class MutableGlobalState : GlobalState { * @param typingEvent [TypingEvent] with information about typing users. Current user is excluded. */ fun tryEmitTypingEvent(cid: String, typingEvent: TypingEvent) { - val typingChannelsCopy = _typingChannels.value.toMutableMap() - - if (typingEvent.users.isEmpty()) { - typingChannelsCopy.remove(cid) - } else { - typingChannelsCopy[cid] = typingEvent + _typingChannels?.let { + it.tryEmit( + it.value.toMutableMap().apply { + if (typingEvent.users.isEmpty()) { + remove(cid) + } else { + this[cid] = typingEvent + } + } + ) } - _typingChannels.tryEmit(typingChannelsCopy) } } diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/querychannels/internal/QueryChannelsMutableState.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/querychannels/internal/QueryChannelsMutableState.kt index 8ece3e9be49..337a7bbf379 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/querychannels/internal/QueryChannelsMutableState.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/querychannels/internal/QueryChannelsMutableState.kt @@ -48,9 +48,9 @@ internal class QueryChannelsMutableState( private val logger by taggedLogger("Chat:QueryChannelsState") internal var rawChannels: Map? - get() = _channels.value + get() = _channels?.value private set(value) { - _channels.value = value + _channels?.value = value } // This is needed for queries @@ -65,16 +65,17 @@ internal class QueryChannelsMutableState( * - emptyMap() - the stat should be [ChannelsStateData.OfflineNoResults] * - notEmptyMap() - the state should be [ChannelsStateData.Result] */ - private val _channels = MutableStateFlow?>(null) - private val _loading = MutableStateFlow(false) - private val _loadingMore = MutableStateFlow(false) + private var _channels: MutableStateFlow?>? = MutableStateFlow(null) + private val mapChannels: StateFlow?> = _channels!! + private var _loading: MutableStateFlow? = MutableStateFlow(false) + private var _loadingMore: MutableStateFlow? = MutableStateFlow(false) - internal val currentLoading: MutableStateFlow - get() = if (channels.value.isNullOrEmpty()) _loading else _loadingMore + internal val currentLoading: StateFlow + get() = if (channels.value.isNullOrEmpty()) loading else loadingMore - private val _endOfChannels = MutableStateFlow(false) - private val _sortedChannels: StateFlow?> = - _channels.combine(latestUsers) { channelMap, userMap -> + private var _endOfChannels: MutableStateFlow? = MutableStateFlow(false) + private val sortedChannels: StateFlow?> = + mapChannels.combine(latestUsers) { channelMap, userMap -> channelMap?.values?.updateUsers(userMap) }.map { channels -> if (channels?.isNotEmpty() == true) { @@ -93,33 +94,34 @@ internal class QueryChannelsMutableState( } } }.stateIn(scope, SharingStarted.Eagerly, null) - private val _currentRequest = MutableStateFlow(null) - private val _recoveryNeeded: MutableStateFlow = MutableStateFlow(false) - internal val channelsOffset: MutableStateFlow = MutableStateFlow(0) + private var _currentRequest: MutableStateFlow? = MutableStateFlow(null) + private var _recoveryNeeded: MutableStateFlow? = MutableStateFlow(false) + private var _channelsOffset: MutableStateFlow? = MutableStateFlow(0) + internal val channelsOffset: StateFlow = _channelsOffset!! override var chatEventHandlerFactory: ChatEventHandlerFactory? = null - override val recoveryNeeded: StateFlow = _recoveryNeeded + override val recoveryNeeded: StateFlow = _recoveryNeeded!! /** * Non-nullable property of [ChatEventHandler] to ensure we always have some handler to handle events. Returns * handler set by user or default one if there is no. */ private val eventHandler: ChatEventHandler by lazy { - (chatEventHandlerFactory ?: ChatEventHandlerFactory()).chatEventHandler(_channels) + (chatEventHandlerFactory ?: ChatEventHandlerFactory()).chatEventHandler(mapChannels) } fun handleChatEvent(event: ChatEvent, cachedChannel: Channel?): EventHandlingResult { return eventHandler.handleChatEvent(event, filter, cachedChannel) } - override val currentRequest: StateFlow = _currentRequest - override val loading: StateFlow = _loading - override val loadingMore: StateFlow = _loadingMore - override val endOfChannels: StateFlow = _endOfChannels - override val channels: StateFlow?> = _sortedChannels + override val currentRequest: StateFlow = _currentRequest!! + override val loading: StateFlow = _loading!! + override val loadingMore: StateFlow = _loadingMore!! + override val endOfChannels: StateFlow = _endOfChannels!! + override val channels: StateFlow?> = sortedChannels override val channelsStateData: StateFlow = - _loading.combine(_sortedChannels) { loading: Boolean, channels: List? -> + loading.combine(sortedChannels) { loading: Boolean, channels: List? -> when { loading || channels == null -> ChannelsStateData.Loading channels.isEmpty() -> ChannelsStateData.OfflineNoResults @@ -136,14 +138,14 @@ internal class QueryChannelsMutableState( * Set loading more. Notifies if the SDK is loading more channels. */ fun setLoadingMore(isLoading: Boolean) { - _loadingMore.value = isLoading + _loadingMore?.value = isLoading } /** * Set loading more. Notifies if the SDK is loading the first page. */ fun setLoadingFirstPage(isLoading: Boolean) { - _loading.value = isLoading + _loading?.value = isLoading } /** @@ -152,7 +154,7 @@ internal class QueryChannelsMutableState( * @param request [QueryChannelsRequest] */ fun setCurrentRequest(request: QueryChannelsRequest) { - _currentRequest.value = request + _currentRequest?.value = request } /** @@ -161,7 +163,7 @@ internal class QueryChannelsMutableState( * @parami isEnd Boolean */ fun setEndOfChannels(isEnd: Boolean) { - _endOfChannels.value = isEnd + _endOfChannels?.value = isEnd } /** @@ -170,7 +172,7 @@ internal class QueryChannelsMutableState( * @param recoveryNeeded Boolean */ fun setRecoveryNeeded(recoveryNeeded: Boolean) { - _recoveryNeeded.value = recoveryNeeded + _recoveryNeeded?.value = recoveryNeeded } /** @@ -179,12 +181,22 @@ internal class QueryChannelsMutableState( * @param offset Int */ fun setChannelsOffset(offset: Int) { - channelsOffset.value = offset + _channelsOffset?.value = offset } fun setChannels(channelsMap: Map) { rawChannels = channelsMap } + + fun destroy() { + _channels = null + _loading = null + _loadingMore = null + _endOfChannels = null + _currentRequest = null + _recoveryNeeded = null + _channelsOffset = null + } } internal fun QueryChannelsState.toMutableState(): QueryChannelsMutableState = this as QueryChannelsMutableState diff --git a/stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt b/stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt index 4b6a5006a60..8d78fe9bd4b 100644 --- a/stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt +++ b/stream-chat-android-state/src/test/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogicTest.kt @@ -71,8 +71,7 @@ internal class ChannelStateLogicTest { @BeforeEach fun setup() { - mutableGlobalState.clearState() - mutableGlobalState.setUser(user) + spyMutableGlobalState = spy(MutableGlobalState().apply { setUser(this@ChannelStateLogicTest.user) }) channelStateLogic = ChannelStateLogic( mutableState, globalMutableState = spyMutableGlobalState, @@ -126,8 +125,7 @@ internal class ChannelStateLogicTest { on(mock.cachedLatestMessages) doReturn _cachedMessages on(mock.quotedMessagesMap) doReturn _quotedMessagesMap } - private val mutableGlobalState = MutableGlobalState() - private val spyMutableGlobalState = spy(mutableGlobalState) + private lateinit var spyMutableGlobalState: MutableGlobalState private val unreadCountLogic: UnreadCountLogic = mock() private val attachmentUrlValidator: AttachmentUrlValidator = mock {