Skip to content

Commit 7611de5

Browse files
authored
Add event handling for the feed created event (#117)
* Handle FeedCreatedEvent * Move Feed filter check to FeedEventHandler * Rename filter parameter to activityFilter * Remove FeedAdded handling for Feed
1 parent 9115d4b commit 7611de5

File tree

14 files changed

+138
-137
lines changed

14 files changed

+138
-137
lines changed

stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedImpl.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ internal class FeedImpl(
111111
memberListState = memberList.mutableState,
112112
)
113113

114-
private val eventHandler = FeedEventHandler(fid = fid, state = _state)
114+
private val eventHandler =
115+
FeedEventHandler(fid = fid, activityFilter = query.activityFilter, state = _state)
115116

116117
init {
117118
subscriptionManager.subscribe(eventHandler)

stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedListImpl.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ internal class FeedListImpl(
4141

4242
private val _state: FeedListStateImpl = FeedListStateImpl(query)
4343

44-
private val eventHandler = FeedListEventHandler(_state)
44+
private val eventHandler = FeedListEventHandler(query.filter, _state)
4545

4646
init {
4747
subscriptionManager.subscribe(eventHandler)

stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedListStateImpl.kt

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.getstream.feeds.android.client.api.state.query.FeedsSort
2323
import io.getstream.feeds.android.client.internal.model.PaginationResult
2424
import io.getstream.feeds.android.client.internal.state.query.FeedsQueryConfig
2525
import io.getstream.feeds.android.client.internal.utils.mergeSorted
26+
import io.getstream.feeds.android.client.internal.utils.upsertSorted
2627
import kotlinx.coroutines.flow.MutableStateFlow
2728
import kotlinx.coroutines.flow.StateFlow
2829
import kotlinx.coroutines.flow.asStateFlow
@@ -55,22 +56,6 @@ internal class FeedListStateImpl(override val query: FeedsQuery) : FeedListMutab
5556
override val pagination: PaginationData?
5657
get() = _pagination
5758

58-
override fun onFeedUpdated(feed: FeedData) {
59-
_feeds.update { current ->
60-
current.map {
61-
if (it.fid == feed.fid) {
62-
feed
63-
} else {
64-
it
65-
}
66-
}
67-
}
68-
}
69-
70-
override fun onFeedRemoved(feedId: String) {
71-
_feeds.update { current -> current.filter { it.fid.rawValue != feedId } }
72-
}
73-
7459
override fun onQueryMoreFeeds(
7560
result: PaginationResult<FeedData>,
7661
queryConfig: FeedsQueryConfig,
@@ -82,6 +67,14 @@ internal class FeedListStateImpl(override val query: FeedsQuery) : FeedListMutab
8267
current.mergeSorted(result.models, { it.fid.rawValue }, feedsSorting)
8368
}
8469
}
70+
71+
override fun onFeedRemoved(feedId: String) {
72+
_feeds.update { current -> current.filter { it.fid.rawValue != feedId } }
73+
}
74+
75+
override fun onFeedUpserted(feed: FeedData) {
76+
_feeds.update { current -> current.upsertSorted(feed, FeedData::fid, feedsSorting) }
77+
}
8578
}
8679

8780
/**
@@ -100,12 +93,12 @@ internal interface FeedListMutableState : FeedListState, FeedListStateUpdates
10093
*/
10194
internal interface FeedListStateUpdates {
10295

103-
/** Handles updates to a specific feed. */
104-
fun onFeedUpdated(feed: FeedData)
105-
10696
/** Handles the result of a query for more feeds. */
10797
fun onQueryMoreFeeds(result: PaginationResult<FeedData>, queryConfig: FeedsQueryConfig)
10898

10999
/** Handles the removal of a feed by its ID. */
110100
fun onFeedRemoved(feedId: String)
101+
102+
/** Handles updates to a specific feed. */
103+
fun onFeedUpserted(feed: FeedData)
111104
}

stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedStateImpl.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package io.getstream.feeds.android.client.internal.state
1717

18-
import io.getstream.android.core.api.filter.matches
1918
import io.getstream.android.core.api.sort.Sort
2019
import io.getstream.feeds.android.client.api.model.ActivityData
2120
import io.getstream.feeds.android.client.api.model.ActivityPinData
@@ -159,16 +158,12 @@ internal class FeedStateImpl(
159158
}
160159

161160
override fun onActivityAdded(activity: ActivityData) {
162-
if (feedQuery.activityFilter?.matches(activity) == false) return
163-
164161
_activities.update { current ->
165162
current.upsertSorted(activity, ActivityData::id, activitiesSorting)
166163
}
167164
}
168165

169166
override fun onActivityUpdated(activity: ActivityData) {
170-
if (feedQuery.activityFilter?.matches(activity) == false) return
171-
172167
updateActivitiesWhere({ it.id == activity.id }) { it.update(activity) }
173168
}
174169

stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/StateUpdateEvent.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import io.getstream.feeds.android.network.models.CommentReactionAddedEvent
5050
import io.getstream.feeds.android.network.models.CommentReactionDeletedEvent
5151
import io.getstream.feeds.android.network.models.CommentReactionUpdatedEvent
5252
import io.getstream.feeds.android.network.models.CommentUpdatedEvent
53+
import io.getstream.feeds.android.network.models.FeedCreatedEvent
5354
import io.getstream.feeds.android.network.models.FeedDeletedEvent
5455
import io.getstream.feeds.android.network.models.FeedMemberAddedEvent
5556
import io.getstream.feeds.android.network.models.FeedMemberRemovedEvent
@@ -140,10 +141,12 @@ internal sealed interface StateUpdateEvent {
140141
val reaction: FeedsReactionData,
141142
) : StateUpdateEvent
142143

143-
data class FeedUpdated(val feed: FeedData) : StateUpdateEvent
144+
data class FeedAdded(val feed: FeedData) : StateUpdateEvent
144145

145146
data class FeedDeleted(val fid: String) : StateUpdateEvent
146147

148+
data class FeedUpdated(val feed: FeedData) : StateUpdateEvent
149+
147150
data class FeedMemberAdded(val fid: String, val member: FeedMemberData) : StateUpdateEvent
148151

149152
data class FeedMemberRemoved(val fid: String, val memberId: String) : StateUpdateEvent
@@ -232,6 +235,8 @@ internal fun WSEvent.toModel(): StateUpdateEvent? =
232235
is CommentReactionUpdatedEvent ->
233236
StateUpdateEvent.CommentReactionUpdated(fid, comment.toModel(), reaction.toModel())
234237

238+
is FeedCreatedEvent -> StateUpdateEvent.FeedAdded(feed.toModel())
239+
235240
is FeedUpdatedEvent -> StateUpdateEvent.FeedUpdated(feed.toModel())
236241

237242
is FeedDeletedEvent -> StateUpdateEvent.FeedDeleted(fid)

stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedEventHandler.kt

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ package io.getstream.feeds.android.client.internal.state.event.handler
1717

1818
import io.getstream.feeds.android.client.api.model.FeedId
1919
import io.getstream.feeds.android.client.api.model.FollowData
20+
import io.getstream.feeds.android.client.api.state.query.ActivitiesFilter
2021
import io.getstream.feeds.android.client.internal.state.FeedStateUpdates
2122
import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent
23+
import io.getstream.feeds.android.client.internal.state.query.matches
2224
import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener
2325

2426
/**
@@ -29,8 +31,11 @@ import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventList
2931
* @param fid The unique identifier for the feed this handler is associated with.
3032
* @property state The instance that manages updates to the feed state.
3133
*/
32-
internal class FeedEventHandler(private val fid: FeedId, private val state: FeedStateUpdates) :
33-
StateUpdateEventListener {
34+
internal class FeedEventHandler(
35+
private val fid: FeedId,
36+
private val activityFilter: ActivitiesFilter?,
37+
private val state: FeedStateUpdates,
38+
) : StateUpdateEventListener {
3439

3540
/**
3641
* Processes a state update event and updates the feed state.
@@ -40,7 +45,7 @@ internal class FeedEventHandler(private val fid: FeedId, private val state: Feed
4045
override fun onEvent(event: StateUpdateEvent) {
4146
when (event) {
4247
is StateUpdateEvent.ActivityAdded -> {
43-
if (event.fid == fid.rawValue) {
48+
if (event.fid == fid.rawValue && event.activity matches activityFilter) {
4449
state.onActivityAdded(event.activity)
4550
}
4651
}
@@ -51,6 +56,12 @@ internal class FeedEventHandler(private val fid: FeedId, private val state: Feed
5156
}
5257
}
5358

59+
is StateUpdateEvent.ActivityUpdated -> {
60+
if (event.fid == fid.rawValue && event.activity matches activityFilter) {
61+
state.onActivityUpdated(event.activity)
62+
}
63+
}
64+
5465
is StateUpdateEvent.ActivityRemovedFromFeed -> {
5566
if (event.fid == fid.rawValue) {
5667
state.onActivityRemoved(event.activityId)
@@ -75,12 +86,6 @@ internal class FeedEventHandler(private val fid: FeedId, private val state: Feed
7586
}
7687
}
7788

78-
is StateUpdateEvent.ActivityUpdated -> {
79-
if (event.fid == fid.rawValue) {
80-
state.onActivityUpdated(event.activity)
81-
}
82-
}
83-
8489
is StateUpdateEvent.ActivityPinned -> {
8590
if (event.fid == fid.rawValue) {
8691
state.onActivityPinned(event.pinnedActivity)

stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedListEventHandler.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,35 @@
1515
*/
1616
package io.getstream.feeds.android.client.internal.state.event.handler
1717

18+
import io.getstream.feeds.android.client.api.state.query.FeedsFilter
1819
import io.getstream.feeds.android.client.internal.state.FeedListStateUpdates
1920
import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent
21+
import io.getstream.feeds.android.client.internal.state.query.matches
2022
import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener
2123

22-
internal class FeedListEventHandler(private val state: FeedListStateUpdates) :
23-
StateUpdateEventListener {
24+
internal class FeedListEventHandler(
25+
private val filter: FeedsFilter?,
26+
private val state: FeedListStateUpdates,
27+
) : StateUpdateEventListener {
2428

2529
override fun onEvent(event: StateUpdateEvent) {
2630
when (event) {
27-
is StateUpdateEvent.FeedUpdated -> {
28-
state.onFeedUpdated(event.feed)
31+
is StateUpdateEvent.FeedAdded -> {
32+
if (event.feed matches filter) {
33+
state.onFeedUpserted(event.feed)
34+
}
2935
}
3036

3137
is StateUpdateEvent.FeedDeleted -> {
3238
state.onFeedRemoved(event.fid)
3339
}
3440

41+
is StateUpdateEvent.FeedUpdated -> {
42+
if (event.feed matches filter) {
43+
state.onFeedUpserted(event.feed)
44+
}
45+
}
46+
3547
else -> {}
3648
}
3749
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-feeds-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.getstream.feeds.android.client.internal.state.query
17+
18+
import io.getstream.android.core.api.filter.Filter
19+
import io.getstream.android.core.api.filter.FilterField
20+
import io.getstream.android.core.api.filter.matches
21+
22+
internal infix fun <M, F : FilterField<M>> M.matches(filter: Filter<M, F>?): Boolean =
23+
// Null filter means "no filter", so everything matches
24+
filter == null || filter.matches(this)

stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/utils/List.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ internal fun <T> MutableList<T>.insertSorted(element: T, sort: List<Sort<T>>): L
133133
* will be updated and repositioned; otherwise, the new element will be inserted in the correct
134134
* sorted position as-is.
135135
*/
136-
internal fun <T> List<T>.upsertSorted(
136+
internal fun <T, ID> List<T>.upsertSorted(
137137
element: T,
138-
idSelector: (T) -> String,
138+
idSelector: (T) -> ID,
139139
comparator: Comparator<in T>,
140140
update: (old: T, new: T) -> T = { _, new -> new },
141141
): List<T> {
@@ -189,9 +189,9 @@ internal fun <T> List<T>.upsertSorted(
189189
* will be updated and repositioned; otherwise, the new element will be inserted in the correct
190190
* sorted position as-is.
191191
*/
192-
internal fun <T> List<T>.upsertSorted(
192+
internal fun <T, ID> List<T>.upsertSorted(
193193
element: T,
194-
idSelector: (T) -> String,
194+
idSelector: (T) -> ID,
195195
sort: List<Sort<T>>,
196196
update: (old: T, new: T) -> T = { _, new -> new },
197197
): List<T> = upsertSorted(element, idSelector, CompositeComparator(sort), update)

stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/FeedListStateImplTest.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ internal class FeedListStateImplTest {
5151
}
5252

5353
@Test
54-
fun `on feedUpdated, then update specific feed`() = runTest {
54+
fun `on onFeedUpserted with existing feed, then update specific feed`() = runTest {
5555
val feed1 = feedData(id = "feed-1", groupId = "user", name = "First Feed")
5656
val feed2 = feedData(id = "feed-2", groupId = "user", name = "Second Feed")
5757
val initialFeeds = listOf(feed1, feed2)
@@ -65,25 +65,26 @@ internal class FeedListStateImplTest {
6565
name = "Updated Feed",
6666
description = "Updated description",
6767
)
68-
feedListState.onFeedUpdated(updatedFeed)
68+
feedListState.onFeedUpserted(updatedFeed)
6969

7070
val updatedFeeds = feedListState.feeds.value
7171
assertEquals(listOf(updatedFeed, feed2), updatedFeeds)
7272
}
7373

7474
@Test
75-
fun `on feedUpdated with non-existent feed, then keep existing feeds unchanged`() = runTest {
76-
val feed1 = feedData(id = "feed-1", groupId = "user", name = "First Feed")
77-
val feed2 = feedData(id = "feed-2", groupId = "user", name = "Second Feed")
75+
fun `on feedUpserted with non-existent feed, then insert feed in sorted position`() = runTest {
76+
val feed1 = feedData(id = "feed-1", groupId = "group", name = "F1", createdAt = 1000)
77+
val feed2 = feedData(id = "feed-2", groupId = "group", name = "F2", createdAt = 2000)
7878
val initialFeeds = listOf(feed1, feed2)
7979
val paginationResult = defaultPaginationResult(initialFeeds)
8080
feedListState.onQueryMoreFeeds(paginationResult, defaultQueryConfig)
8181

82-
val nonExistentFeed =
83-
feedData(id = "non-existent", groupId = "user", name = "Non-existent Feed")
84-
feedListState.onFeedUpdated(nonExistentFeed)
82+
val newFeed = feedData(id = "feed-new", groupId = "group", name = "F1.5", createdAt = 1500)
83+
feedListState.onFeedUpserted(newFeed)
8584

86-
assertEquals(initialFeeds, feedListState.feeds.value)
85+
// Expect the new feed to be inserted in a sorted position based on createdAt
86+
val expectedFeeds = listOf(feed1, newFeed, feed2)
87+
assertEquals(expectedFeeds, feedListState.feeds.value)
8788
}
8889

8990
@Test

0 commit comments

Comments
 (0)