Skip to content

Commit cdd24bf

Browse files
JcMinarroPetarVelikovVelikovPetar
authored
Feature/simple livestream channel optimization poc (#5887)
* Livestream channel optimization POC. * Livestream channel optimization POC. * Move logic to ChannelMutableState.kt. * Move logic to ChannelMutableState.kt. * Simplify limit multiplier logic and add test. * Remove clearing of messages and add KDocs. Remove default livestream setup. * Apply spotless. * Remove "livestream" from channels query. * Update CHANGELOG.md. * Remove logs. --------- Co-authored-by: PetarVelikov <[email protected]> Co-authored-by: Petar Velikov <[email protected]>
1 parent af9c417 commit cdd24bf

File tree

11 files changed

+398
-59
lines changed

11 files changed

+398
-59
lines changed

CHANGELOG.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,22 @@
3030

3131
### ✅ Added
3232
- Add `StreamOfflinePluginFactory.ignoredChannelTypes` property to allow ignoring messages for specific channel types in the offline storage. [#5877](https://github.com/GetStream/stream-chat-android/pull/5877)
33-
- By default the `StreamOfflinePluginFactory.ignoredChannelTypes` is set to `livestream` channel types, so that the messages that belong to `livestream` channel type are not stored in the offline storage. [#5877](https://github.com/GetStream/stream-chat-android/pull/5877)
3433

3534
### ⚠️ Changed
3635

3736
### ❌ Removed
3837

3938
## stream-chat-android-state
4039
### 🐞 Fixed
41-
- Fix reactions not working in `livestream` channels. [#5883](https://github.com/GetStream/stream-chat-android/pull/5883)
42-
- Fix attachments upload not working in channels with message limit applied. [#5886](https://github.com/GetStream/stream-chat-android/pull/5886)
40+
- Fix reactions not working in channels with disabled DB writes. [#5883](https://github.com/GetStream/stream-chat-android/pull/5883)
4341
- Fix delete message not working in channels with disabled DB writes. [#5886](https://github.com/GetStream/stream-chat-android/pull/5886)
42+
- Fix attachments upload not working in channels with message limit applied. [#5886](https://github.com/GetStream/stream-chat-android/pull/5886)
4443
- Fix thread not showing the parent message when offline support is disabled. [#5891](https://github.com/GetStream/stream-chat-android/pull/5891)
4544

4645
### ⬆️ Improved
4746

4847
### ✅ Added
4948
- Add `StatePluginConfig.MessageLimitConfig` config to allow setting the maximum number of messages to be kept in memory for specific channel types. [#5877](https://github.com/GetStream/stream-chat-android/pull/5877)
50-
- By default the `StatePluginConfig.MessageLimitConfig` is set to 500 messages for `livestream` channel types. [#5877](https://github.com/GetStream/stream-chat-android/pull/5877)
5149

5250
### ⚠️ Changed
5351
- Deprecate `SendReactionListener.onSendReactionPrecondition(currentUser?, reaction: Reaction)` in favor of `SendReactionListener.onSendReactionPrecondition(cid: String?, currentUser: User?, reaction: Reaction)`. [#5883](https://github.com/GetStream/stream-chat-android/pull/5883)

stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/plugin/factory/StreamOfflinePluginFactory.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ import kotlin.reflect.KClass
6666
*/
6767
public class StreamOfflinePluginFactory @JvmOverloads constructor(
6868
private val appContext: Context,
69-
private val ignoredChannelTypes: Set<String> = setOf("livestream"),
69+
private val ignoredChannelTypes: Set<String> = emptySet(),
7070
private val now: () -> Long = { System.currentTimeMillis() },
7171
) : PluginFactory, RepositoryFactory.Provider {
7272

stream-chat-android-state/api/stream-chat-android-state.api

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ public final class io/getstream/chat/android/state/plugin/config/ChannelMessageL
103103
public final fun copy (Ljava/lang/String;I)Lio/getstream/chat/android/state/plugin/config/ChannelMessageLimit;
104104
public static synthetic fun copy$default (Lio/getstream/chat/android/state/plugin/config/ChannelMessageLimit;Ljava/lang/String;IILjava/lang/Object;)Lio/getstream/chat/android/state/plugin/config/ChannelMessageLimit;
105105
public fun equals (Ljava/lang/Object;)Z
106+
public final fun getBaseLimit ()I
106107
public final fun getChannelType ()Ljava/lang/String;
107-
public final fun getLimit ()I
108108
public fun hashCode ()I
109109
public fun toString ()Ljava/lang/String;
110110
}

stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/config/StatePluginConfig.kt

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,30 +35,26 @@ public data class StatePluginConfig @JvmOverloads constructor(
3535
public val messageLimitConfig: MessageLimitConfig = MessageLimitConfig(),
3636
)
3737

38-
private const val MESSAGE_LIMIT = 500
39-
4038
/**
4139
* Configuration for message limits in channels.
4240
*
4341
* @param channelMessageLimits A set of [ChannelMessageLimit] defining the maximum number of messages to keep in
4442
* memory for different channel types.
4543
* This configuration allows you to specify the maximum number of messages to keep in memory for different
4644
* channel types.
47-
* By default, it includes a limit for "livestream" channels with a maximum of 1000 messages.
45+
* By default, no limits per channel type are applied, meaning all messages will be kept in memory.
4846
*/
4947
public data class MessageLimitConfig(
50-
public val channelMessageLimits: Set<ChannelMessageLimit> = setOf(
51-
ChannelMessageLimit("livestream", MESSAGE_LIMIT),
52-
),
48+
public val channelMessageLimits: Set<ChannelMessageLimit> = setOf(),
5349
)
5450

5551
/**
5652
* Configuration for message limits in channels, specifying the channel type and limit.
5753
*
5854
* @param channelType The type of the channel for which the limit applies.
59-
* @param limit The maximum number of messages to keep in memory for the channel.
55+
* @param baseLimit The initial maximum number of messages to keep in memory for the channel.
6056
*/
6157
public data class ChannelMessageLimit(
6258
public val channelType: String,
63-
public val limit: Int,
59+
public val baseLimit: Int,
6460
)

stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/listener/internal/QueryChannelListenerState.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ internal class QueryChannelListenerState(private val logic: LogicRegistry) : Que
5252
request: QueryChannelRequest,
5353
) {
5454
logger.d { "[onQueryChannelRequest] cid: $channelType:$channelId, request: $request" }
55-
logic.channel(channelType, channelId).updateStateFromDatabase(request)
55+
logic.channel(channelType, channelId).apply {
56+
updateStateFromDatabase(request)
57+
setPaginationDirection(request)
58+
}
5659
}
5760

5861
/**

stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelLogic.kt

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ internal class ChannelLogic(
145145
}
146146
}
147147

148+
fun setPaginationDirection(request: QueryChannelRequest) {
149+
when {
150+
request.filteringOlderMessages() -> channelStateLogic.loadingOlderMessages()
151+
request.isFilteringNewerMessages() -> channelStateLogic.loadingNewerMessages()
152+
!request.isFilteringMessages() -> channelStateLogic.loadingNewestMessages()
153+
}
154+
}
155+
148156
/**
149157
* Returns the state of Channel. Useful to check how it the state of the channel of the [ChannelLogic]
150158
*
@@ -173,6 +181,7 @@ internal class ChannelLogic(
173181
),
174182
)
175183
}
184+
channelStateLogic.loadingNewestMessages()
176185
return runChannelQuery(
177186
"watch",
178187
QueryChannelPaginationRequest(messagesLimit).toWatchChannelRequest(userPresence).apply {
@@ -391,11 +400,6 @@ internal class ChannelLogic(
391400
channelStateLogic.upsertMessage(message)
392401
}
393402

394-
internal fun upsertMessages(messages: List<Message>) {
395-
logger.d { "[upsertMessages] messages.size: ${messages.size}" }
396-
channelStateLogic.upsertMessages(messages)
397-
}
398-
399403
/**
400404
* Returns instance of [WatchChannelRequest] to obtain older messages of a channel.
401405
*
@@ -541,6 +545,7 @@ internal class ChannelLogic(
541545
channelStateLogic.updateCurrentUserRead(event.createdAt, event.message)
542546
channelStateLogic.takeUnless { event.message.shadowed }?.toggleHidden(false)
543547
}
548+
544549
is MessageUpdatedEvent -> {
545550
event.message.copy(
546551
replyTo = event.message.replyMessageId
@@ -549,6 +554,7 @@ internal class ChannelLogic(
549554
).let(::upsertEventMessage)
550555
channelStateLogic.toggleHidden(false)
551556
}
557+
552558
is MessageDeletedEvent -> {
553559
if (event.hardDelete) {
554560
deleteMessage(event.message)
@@ -557,13 +563,15 @@ internal class ChannelLogic(
557563
}
558564
channelStateLogic.toggleHidden(false)
559565
}
566+
560567
is NotificationMessageNewEvent -> {
561568
if (!mutableState.insideSearch.value) {
562569
upsertEventMessage(event.message)
563570
}
564571
channelStateLogic.updateCurrentUserRead(event.createdAt, event.message)
565572
channelStateLogic.toggleHidden(false)
566573
}
574+
567575
is NotificationThreadMessageNewEvent -> upsertEventMessage(event.message)
568576
is ReactionNewEvent -> upsertEventMessage(event.message)
569577
is ReactionUpdateEvent -> upsertEventMessage(event.message)
@@ -575,24 +583,29 @@ internal class ChannelLogic(
575583
channelStateLogic.addMembership(event.member)
576584
}
577585
}
586+
578587
is MemberRemovedEvent -> {
579588
channelStateLogic.deleteMember(event.member)
580589
// Remove the channel.membership if the current user is removed from the channel
581590
if (event.member.getUserId() == currentUserId) {
582591
channelStateLogic.removeMembership()
583592
}
584593
}
594+
585595
is MemberUpdatedEvent -> {
586596
channelStateLogic.upsertMember(event.member)
587597
channelStateLogic.updateMembership(event.member)
588598
}
599+
589600
is NotificationAddedToChannelEvent -> {
590601
channelStateLogic.upsertMembers(event.channel.members)
591602
}
603+
592604
is NotificationRemovedFromChannelEvent -> {
593605
channelStateLogic.setMembers(event.channel.members, event.channel.memberCount)
594606
channelStateLogic.setWatchers(event.channel.watchers, event.channel.watcherCount)
595607
}
608+
596609
is UserStartWatchingEvent -> channelStateLogic.upsertWatcher(event)
597610
is UserStopWatchingEvent -> channelStateLogic.deleteWatcher(event)
598611
is ChannelUpdatedEvent -> channelStateLogic.updateChannelData(event)
@@ -603,30 +616,36 @@ internal class ChannelLogic(
603616
removeMessagesBefore(event.createdAt)
604617
}
605618
}
619+
606620
is ChannelVisibleEvent -> channelStateLogic.toggleHidden(false)
607621
is ChannelDeletedEvent -> {
608622
removeMessagesBefore(event.createdAt)
609623
channelStateLogic.deleteChannel(event.createdAt)
610624
}
625+
611626
is ChannelTruncatedEvent -> removeMessagesBefore(event.createdAt, event.message)
612627
is NotificationChannelTruncatedEvent -> removeMessagesBefore(event.createdAt)
613628
is TypingStopEvent -> channelStateLogic.setTyping(event.user.id, null)
614629
is TypingStartEvent -> channelStateLogic.setTyping(event.user.id, event)
615630
is MessageReadEvent -> if (event.thread == null) {
616631
channelStateLogic.updateRead(event.toChannelUserRead())
617632
}
633+
618634
is NotificationMarkReadEvent -> if (event.thread == null) {
619635
channelStateLogic.updateRead(event.toChannelUserRead())
620636
}
637+
621638
is NotificationMarkUnreadEvent -> channelStateLogic.updateRead(event.toChannelUserRead())
622639
is NotificationInviteAcceptedEvent -> {
623640
channelStateLogic.addMember(event.member)
624641
channelStateLogic.updateChannelData(event)
625642
}
643+
626644
is NotificationInviteRejectedEvent -> {
627645
channelStateLogic.deleteMember(event.member)
628646
channelStateLogic.updateChannelData(event)
629647
}
648+
630649
is ChannelUserBannedEvent -> {
631650
channelStateLogic.updateMemberBanned(
632651
memberUserId = event.user.id,
@@ -635,6 +654,7 @@ internal class ChannelLogic(
635654
shadow = event.shadow,
636655
)
637656
}
657+
638658
is ChannelUserUnbannedEvent -> {
639659
channelStateLogic.updateMemberBanned(
640660
memberUserId = event.user.id,
@@ -643,13 +663,16 @@ internal class ChannelLogic(
643663
shadow = false,
644664
)
645665
}
666+
646667
is PollClosedEvent -> channelStateLogic.upsertPoll(event.processPoll(channelStateLogic::getPoll))
647668
is PollUpdatedEvent -> channelStateLogic.upsertPoll(event.processPoll(channelStateLogic::getPoll))
648669
is PollDeletedEvent -> channelStateLogic.deletePoll(event.poll)
649670
is VoteCastedEvent ->
650671
channelStateLogic.upsertPoll(event.processPoll(currentUserId, channelStateLogic::getPoll))
672+
651673
is VoteChangedEvent ->
652674
channelStateLogic.upsertPoll(event.processPoll(currentUserId, channelStateLogic::getPoll))
675+
653676
is VoteRemovedEvent -> channelStateLogic.upsertPoll(event.processPoll(channelStateLogic::getPoll))
654677
is AnswerCastedEvent -> channelStateLogic.upsertPoll(event.processPoll(channelStateLogic::getPoll))
655678
is ReminderCreatedEvent -> upsertReminder(event.messageId, event.reminder)
@@ -671,6 +694,7 @@ internal class ChannelLogic(
671694
is NotificationChannelMutesUpdatedEvent -> event.me.channelMutes.any { mute ->
672695
mute.channel?.cid == mutableState.cid
673696
}.let(channelStateLogic::updateMute)
697+
674698
is ConnectedEvent,
675699
is ConnectionErrorEvent,
676700
is ConnectingEvent,
@@ -687,10 +711,4 @@ internal class ChannelLogic(
687711
-> Unit // Ignore these events
688712
}
689713
}
690-
691-
fun toChannel(): Channel = mutableState.toChannel()
692-
693-
internal fun replyMessage(repliedMessage: Message?) {
694-
channelStateLogic.replyMessage(repliedMessage)
695-
}
696714
}

stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/logic/channel/internal/ChannelStateLogic.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ internal class ChannelStateLogic(
348348
/**
349349
* Returns all the replies of a quoted message.
350350
*/
351-
public fun getAllReplies(message: Message): List<Message>? {
351+
fun getAllReplies(message: Message): List<Message>? {
352352
return mutableState.quotedMessagesMap
353353
.value[message.id]
354354
?.mapNotNull(mutableState::getMessageById)
@@ -741,6 +741,14 @@ internal class ChannelStateLogic(
741741
}
742742
}
743743

744+
/**
745+
* Called when the user is loading the newest messages.
746+
* Resets the current message limit.
747+
*/
748+
fun loadingNewestMessages() {
749+
mutableState.resetMessageLimit()
750+
}
751+
744752
/**
745753
* Set states as loading newer messages.
746754
*/

stream-chat-android-state/src/main/java/io/getstream/chat/android/state/plugin/state/StateRegistry.kt

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import io.getstream.chat.android.client.events.NotificationChannelDeletedEvent
2222
import io.getstream.chat.android.models.Channel
2323
import io.getstream.chat.android.models.FilterObject
2424
import io.getstream.chat.android.models.Location
25-
import io.getstream.chat.android.models.Message
2625
import io.getstream.chat.android.models.User
2726
import io.getstream.chat.android.models.querysort.QuerySorter
2827
import io.getstream.chat.android.state.event.handler.internal.batch.BatchEvent
@@ -49,6 +48,7 @@ import java.util.concurrent.ConcurrentHashMap
4948
* @param activeLiveLocations Latest live locations of the SDK.
5049
* @param job A background job cancelled after calling [clear].
5150
* @param scope A scope for new coroutines.
51+
* @param messageLimitConfig Configuration for message limits.
5252
*/
5353
@Suppress("LongParameterList")
5454
public class StateRegistry constructor(
@@ -62,7 +62,6 @@ public class StateRegistry constructor(
6262
) {
6363

6464
private val logger by taggedLogger("Chat:StateRegistry")
65-
private val noOpMessagesLimitFilter: (Collection<Message>) -> Collection<Message> = { it }
6665

6766
private val queryChannels: ConcurrentHashMap<Pair<FilterObject, QuerySorter<Channel>>, QueryChannelsMutableState> =
6867
ConcurrentHashMap()
@@ -107,14 +106,17 @@ public class StateRegistry constructor(
107106
*/
108107
internal fun mutableChannel(channelType: String, channelId: String): ChannelMutableState {
109108
return channels.getOrPut(channelType to channelId) {
109+
val baseMessageLimit = messageLimitConfig.channelMessageLimits
110+
.find { it.channelType == channelType }
111+
?.baseLimit
110112
ChannelMutableState(
111-
channelType,
112-
channelId,
113-
userStateFlow,
114-
latestUsers,
115-
activeLiveLocations,
116-
getMessageLimitFilter(channelType),
117-
now,
113+
channelType = channelType,
114+
channelId = channelId,
115+
userFlow = userStateFlow,
116+
latestUsers = latestUsers,
117+
activeLiveLocations = activeLiveLocations,
118+
baseMessageLimit = baseMessageLimit,
119+
now = now,
118120
)
119121
}
120122
}
@@ -203,16 +205,4 @@ public class StateRegistry constructor(
203205
}
204206
logger.i { "[removeChanel] removed channel($channelType, $channelId): $removed" }
205207
}
206-
207-
private fun getMessageLimitFilter(channelType: String): (Collection<Message>) -> Collection<Message> =
208-
messageLimitConfig.channelMessageLimits.firstOrNull { it.channelType == channelType }
209-
?.let { createMessageLimitFilter(it.limit) }
210-
?: noOpMessagesLimitFilter
211-
212-
private fun createMessageLimitFilter(limit: Int): (Collection<Message>) -> Collection<Message> = {
213-
when (it.size > limit) {
214-
true -> it.sortedBy { it.createdAt ?: it.createdLocallyAt }.takeLast(limit)
215-
false -> it
216-
}
217-
}
218208
}

0 commit comments

Comments
 (0)