Skip to content

Commit

Permalink
Create a destroy() method for our States (#4880)
Browse files Browse the repository at this point in the history
* 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`
  • Loading branch information
JcMinarro authored Jul 17, 2023
1 parent 5fd6677 commit cb3a62e
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 205 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,6 @@ internal class LogicRegistry internal constructor(
queryChannels.clear()
channels.clear()
threads.clear()
mutableGlobalState.clearState()
mutableGlobalState.destroy()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -29,43 +29,51 @@ internal class ThreadMutableState(
override val parentId: String,
scope: CoroutineScope,
) : ThreadState {
private val _messages = MutableStateFlow(emptyMap<String, Message>())
private val _loading = MutableStateFlow(false)
private val _loadingOlderMessages = MutableStateFlow(false)
private val _endOfOlderMessages = MutableStateFlow(false)
private val _oldestInThread: MutableStateFlow<Message?> = MutableStateFlow(null)
private var _messages: MutableStateFlow<Map<String, Message>>? = MutableStateFlow(emptyMap())
private var _loading: MutableStateFlow<Boolean>? = MutableStateFlow(false)
private var _loadingOlderMessages: MutableStateFlow<Boolean>? = MutableStateFlow(false)
private var _endOfOlderMessages: MutableStateFlow<Boolean>? = MutableStateFlow(false)
private var _oldestInThread: MutableStateFlow<Message?>? = MutableStateFlow(null)

val rawMessage: StateFlow<Map<String, Message>> = _messages
override val messages: StateFlow<List<Message>> = _messages
val rawMessage: StateFlow<Map<String, Message>> = _messages!!
override val messages: StateFlow<List<Message>> = rawMessage
.map { it.values }
.map { threadMessages -> threadMessages.sortedBy { m -> m.createdAt ?: m.createdLocallyAt } }
.stateIn(scope, SharingStarted.Eagerly, emptyList())
override val loading: StateFlow<Boolean> = _loading
override val loadingOlderMessages: StateFlow<Boolean> = _loadingOlderMessages
override val endOfOlderMessages: StateFlow<Boolean> = _endOfOlderMessages
override val oldestInThread: StateFlow<Message?> = _oldestInThread
override val loading: StateFlow<Boolean> = _loading!!
override val loadingOlderMessages: StateFlow<Boolean> = _loadingOlderMessages!!
override val endOfOlderMessages: StateFlow<Boolean> = _endOfOlderMessages!!
override val oldestInThread: StateFlow<Message?> = _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<Message>) {
_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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<List<Mute>>(emptyList())
private val _channelMutes = MutableStateFlow<List<ChannelMute>>(emptyList())
private val _typingChannels = MutableStateFlow(emptyMap<String, TypingEvent>())

private val _user = MutableStateFlow<User?>(null)

override val totalUnreadCount: StateFlow<Int> = _totalUnreadCount

override val channelUnreadCount: StateFlow<Int> = _channelUnreadCount

override val muted: StateFlow<List<Mute>> = _mutedUsers

override val channelMutes: StateFlow<List<ChannelMute>> = _channelMutes

override val banned: StateFlow<Boolean> = _banned

override val typingChannels: StateFlow<Map<String, TypingEvent>> = _typingChannels

override val user: StateFlow<User?> = _user
private var _totalUnreadCount: MutableStateFlow<Int>? = MutableStateFlow(0)
private var _channelUnreadCount: MutableStateFlow<Int>? = MutableStateFlow(0)
private var _banned: MutableStateFlow<Boolean>? = MutableStateFlow(false)
private var _mutedUsers: MutableStateFlow<List<Mute>>? = MutableStateFlow(emptyList())
private var _channelMutes: MutableStateFlow<List<ChannelMute>>? = MutableStateFlow(emptyList())
private var _typingChannels: MutableStateFlow<Map<String, TypingEvent>>? = MutableStateFlow(emptyMap())
private var _user: MutableStateFlow<User?>? = MutableStateFlow(null)

override val totalUnreadCount: StateFlow<Int> = _totalUnreadCount!!
override val channelUnreadCount: StateFlow<Int> = _channelUnreadCount!!
override val muted: StateFlow<List<Mute>> = _mutedUsers!!
override val channelMutes: StateFlow<List<ChannelMute>> = _channelMutes!!
override val banned: StateFlow<Boolean> = _banned!!
override val typingChannels: StateFlow<Map<String, TypingEvent>> = _typingChannels!!
override val user: StateFlow<User?> = _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<ChannelMute>) {
_channelMutes.value = channelMutes
_channelMutes?.value = channelMutes
}

fun setMutedUsers(mutedUsers: List<Mute>) {
_mutedUsers.value = mutedUsers
_mutedUsers?.value = mutedUsers
}

/**
Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ internal class QueryChannelsMutableState(
private val logger by taggedLogger("Chat:QueryChannelsState")

internal var rawChannels: Map<String, Channel>?
get() = _channels.value
get() = _channels?.value
private set(value) {
_channels.value = value
_channels?.value = value
}

// This is needed for queries
Expand All @@ -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<Map<String, Channel>?>(null)
private val _loading = MutableStateFlow(false)
private val _loadingMore = MutableStateFlow(false)
private var _channels: MutableStateFlow<Map<String, Channel>?>? = MutableStateFlow(null)
private val mapChannels: StateFlow<Map<String, Channel>?> = _channels!!
private var _loading: MutableStateFlow<Boolean>? = MutableStateFlow(false)
private var _loadingMore: MutableStateFlow<Boolean>? = MutableStateFlow(false)

internal val currentLoading: MutableStateFlow<Boolean>
get() = if (channels.value.isNullOrEmpty()) _loading else _loadingMore
internal val currentLoading: StateFlow<Boolean>
get() = if (channels.value.isNullOrEmpty()) loading else loadingMore

private val _endOfChannels = MutableStateFlow(false)
private val _sortedChannels: StateFlow<List<Channel>?> =
_channels.combine(latestUsers) { channelMap, userMap ->
private var _endOfChannels: MutableStateFlow<Boolean>? = MutableStateFlow(false)
private val sortedChannels: StateFlow<List<Channel>?> =
mapChannels.combine(latestUsers) { channelMap, userMap ->
channelMap?.values?.updateUsers(userMap)
}.map { channels ->
if (channels?.isNotEmpty() == true) {
Expand All @@ -93,33 +94,34 @@ internal class QueryChannelsMutableState(
}
}
}.stateIn(scope, SharingStarted.Eagerly, null)
private val _currentRequest = MutableStateFlow<QueryChannelsRequest?>(null)
private val _recoveryNeeded: MutableStateFlow<Boolean> = MutableStateFlow(false)
internal val channelsOffset: MutableStateFlow<Int> = MutableStateFlow(0)
private var _currentRequest: MutableStateFlow<QueryChannelsRequest?>? = MutableStateFlow(null)
private var _recoveryNeeded: MutableStateFlow<Boolean>? = MutableStateFlow(false)
private var _channelsOffset: MutableStateFlow<Int>? = MutableStateFlow(0)
internal val channelsOffset: StateFlow<Int> = _channelsOffset!!

override var chatEventHandlerFactory: ChatEventHandlerFactory? = null

override val recoveryNeeded: StateFlow<Boolean> = _recoveryNeeded
override val recoveryNeeded: StateFlow<Boolean> = _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<QueryChannelsRequest?> = _currentRequest
override val loading: StateFlow<Boolean> = _loading
override val loadingMore: StateFlow<Boolean> = _loadingMore
override val endOfChannels: StateFlow<Boolean> = _endOfChannels
override val channels: StateFlow<List<Channel>?> = _sortedChannels
override val currentRequest: StateFlow<QueryChannelsRequest?> = _currentRequest!!
override val loading: StateFlow<Boolean> = _loading!!
override val loadingMore: StateFlow<Boolean> = _loadingMore!!
override val endOfChannels: StateFlow<Boolean> = _endOfChannels!!
override val channels: StateFlow<List<Channel>?> = sortedChannels
override val channelsStateData: StateFlow<ChannelsStateData> =
_loading.combine(_sortedChannels) { loading: Boolean, channels: List<Channel>? ->
loading.combine(sortedChannels) { loading: Boolean, channels: List<Channel>? ->
when {
loading || channels == null -> ChannelsStateData.Loading
channels.isEmpty() -> ChannelsStateData.OfflineNoResults
Expand All @@ -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
}

/**
Expand All @@ -152,7 +154,7 @@ internal class QueryChannelsMutableState(
* @param request [QueryChannelsRequest]
*/
fun setCurrentRequest(request: QueryChannelsRequest) {
_currentRequest.value = request
_currentRequest?.value = request
}

/**
Expand All @@ -161,7 +163,7 @@ internal class QueryChannelsMutableState(
* @parami isEnd Boolean
*/
fun setEndOfChannels(isEnd: Boolean) {
_endOfChannels.value = isEnd
_endOfChannels?.value = isEnd
}

/**
Expand All @@ -170,7 +172,7 @@ internal class QueryChannelsMutableState(
* @param recoveryNeeded Boolean
*/
fun setRecoveryNeeded(recoveryNeeded: Boolean) {
_recoveryNeeded.value = recoveryNeeded
_recoveryNeeded?.value = recoveryNeeded
}

/**
Expand All @@ -179,12 +181,22 @@ internal class QueryChannelsMutableState(
* @param offset Int
*/
fun setChannelsOffset(offset: Int) {
channelsOffset.value = offset
_channelsOffset?.value = offset
}

fun setChannels(channelsMap: Map<String, Channel>) {
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
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit cb3a62e

Please sign in to comment.