Skip to content

Commit 6d2b364

Browse files
authored
Add handling of missing activity events (#120)
* Handle ActivityDeleted in ActivityEventHandler * Handle activity upserted for ActivityList * Merge activity added & updated handling and add it where missing * Add missing doc for param
1 parent be58158 commit 6d2b364

File tree

14 files changed

+145
-89
lines changed

14 files changed

+145
-89
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ internal class ActivityListImpl(
4747

4848
private val _state: ActivityListStateImpl = ActivityListStateImpl(query, currentUserId)
4949

50-
private val eventHandler = ActivityListEventHandler(state = _state)
50+
private val eventHandler = ActivityListEventHandler(query.filter, state = _state)
5151

5252
init {
5353
subscriptionManager.subscribe(eventHandler)

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ internal class ActivityListStateImpl(
8989
_activities.update { current -> current.filter { it.id != activityId } }
9090
}
9191

92-
override fun onActivityUpdated(activity: ActivityData) {
92+
override fun onActivityUpserted(activity: ActivityData) {
9393
_activities.update { current ->
9494
current.upsertSorted(activity, ActivityData::id, activitiesSorting)
9595
}
@@ -198,11 +198,11 @@ internal interface ActivityListStateUpdates {
198198
fun onActivityRemoved(activityId: String)
199199

200200
/**
201-
* Called when an activity is updated in the list.
201+
* Called when an activity is added to or updated in the list.
202202
*
203-
* @param activity The updated activity data.
203+
* @param activity The activity data.
204204
*/
205-
fun onActivityUpdated(activity: ActivityData)
205+
fun onActivityUpserted(activity: ActivityData)
206206

207207
/**
208208
* Called when a bookmark was removed.

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ internal class ActivityStateImpl(
6262
override val poll: StateFlow<PollData?>
6363
get() = _poll.asStateFlow()
6464

65+
override fun onActivityRemoved() {
66+
_activity.update { null }
67+
_poll.update { null }
68+
}
69+
6570
override fun onActivityUpdated(activity: ActivityData) {
6671
_activity.update { current -> current?.update(activity) ?: activity }
6772
_poll.update { current ->
@@ -130,6 +135,8 @@ internal interface ActivityMutableState : ActivityState, ActivityStateUpdates
130135
* updated, closed, deleted, or when votes are casted, changed, or removed.
131136
*/
132137
internal interface ActivityStateUpdates {
138+
/** Called when the activity is removed. */
139+
fun onActivityRemoved()
133140

134141
/**
135142
* Called when the activity is updated.

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

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,18 @@ internal class FeedStateImpl(
157157
}
158158
}
159159

160-
override fun onActivityAdded(activity: ActivityData) {
161-
_activities.update { current ->
162-
current.upsertSorted(activity, ActivityData::id, activitiesSorting)
160+
override fun onActivityUpserted(activity: ActivityData) {
161+
if (_activities.value.any { it.id == activity.id }) {
162+
updateActivitiesWhere({ it.id == activity.id }) { existingActivity ->
163+
existingActivity.update(activity)
164+
}
165+
} else {
166+
_activities.update { current ->
167+
current.upsertSorted(activity, ActivityData::id, activitiesSorting)
168+
}
163169
}
164170
}
165171

166-
override fun onActivityUpdated(activity: ActivityData) {
167-
updateActivitiesWhere({ it.id == activity.id }) { it.update(activity) }
168-
}
169-
170172
override fun onActivityRemoved(activityId: String) {
171173
_activities.update { current -> current.filter { it.id != activityId } }
172174
// Also remove the activity from pinned activities if it exists
@@ -368,11 +370,8 @@ internal interface FeedStateUpdates {
368370
queryConfig: ActivitiesQueryConfig,
369371
)
370372

371-
/** Handles updates to the feed state when activity is added. */
372-
fun onActivityAdded(activity: ActivityData)
373-
374-
/** Handles updates to the feed state when activity is updated. */
375-
fun onActivityUpdated(activity: ActivityData)
373+
/** Handles updates to the feed state when activity is added or updated. */
374+
fun onActivityUpserted(activity: ActivityData)
376375

377376
/** Handles updates to the feed state when an activity is removed. */
378377
fun onActivityRemoved(activityId: String)

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ internal class ActivityEventHandler(
4141
*/
4242
override fun onEvent(event: StateUpdateEvent) {
4343
when (event) {
44+
is StateUpdateEvent.ActivityDeleted -> {
45+
if (event.fid != fid.rawValue || event.activityId != activityId) return
46+
state.onActivityRemoved()
47+
}
48+
49+
is StateUpdateEvent.ActivityUpdated -> {
50+
if (event.fid != fid.rawValue || event.activity.id != activityId) return
51+
state.onActivityUpdated(event.activity)
52+
}
53+
4454
is StateUpdateEvent.ActivityReactionAdded -> {
4555
if (event.fid != fid.rawValue || event.reaction.activityId != activityId) return
4656
state.onReactionUpserted(event.reaction, event.activity)
@@ -56,11 +66,6 @@ internal class ActivityEventHandler(
5666
state.onReactionUpserted(event.reaction, event.activity)
5767
}
5868

59-
is StateUpdateEvent.ActivityUpdated -> {
60-
if (event.fid != fid.rawValue || event.activity.id != activityId) return
61-
state.onActivityUpdated(event.activity)
62-
}
63-
6469
is StateUpdateEvent.BookmarkAdded -> {
6570
val eventActivity = event.bookmark.activity
6671
if (fid.rawValue !in eventActivity.feeds || eventActivity.id != activityId) return

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

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

18+
import io.getstream.feeds.android.client.api.state.query.ActivitiesFilter
1819
import io.getstream.feeds.android.client.internal.state.ActivityListStateUpdates
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 ActivityListEventHandler(private val state: ActivityListStateUpdates) :
23-
StateUpdateEventListener {
24+
internal class ActivityListEventHandler(
25+
private val filter: ActivitiesFilter?,
26+
private val state: ActivityListStateUpdates,
27+
) : StateUpdateEventListener {
2428

2529
override fun onEvent(event: StateUpdateEvent) {
2630
when (event) {
31+
is StateUpdateEvent.ActivityAdded -> {
32+
if (event.activity matches filter) {
33+
state.onActivityUpserted(event.activity)
34+
}
35+
}
36+
2737
is StateUpdateEvent.ActivityDeleted -> state.onActivityRemoved(event.activityId)
38+
is StateUpdateEvent.ActivityUpdated -> {
39+
if (event.activity matches filter) {
40+
state.onActivityUpserted(event.activity)
41+
}
42+
}
43+
2844
is StateUpdateEvent.ActivityReactionAdded ->
2945
state.onReactionUpserted(event.reaction, event.activity)
3046

@@ -47,6 +63,7 @@ internal class ActivityListEventHandler(private val state: ActivityListStateUpda
4763

4864
is StateUpdateEvent.CommentReactionUpdated ->
4965
state.onCommentReactionUpserted(event.comment, event.reaction)
66+
5067
else -> {
5168
// No action needed for other event types
5269
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventList
2929
* other feed-related actions.
3030
*
3131
* @param fid The unique identifier for the feed this handler is associated with.
32+
* @param activityFilter An optional filter to determine which activities should be processed.
3233
* @property state The instance that manages updates to the feed state.
3334
*/
3435
internal class FeedEventHandler(
@@ -46,7 +47,7 @@ internal class FeedEventHandler(
4647
when (event) {
4748
is StateUpdateEvent.ActivityAdded -> {
4849
if (event.fid == fid.rawValue && event.activity matches activityFilter) {
49-
state.onActivityAdded(event.activity)
50+
state.onActivityUpserted(event.activity)
5051
}
5152
}
5253

@@ -58,7 +59,7 @@ internal class FeedEventHandler(
5859

5960
is StateUpdateEvent.ActivityUpdated -> {
6061
if (event.fid == fid.rawValue && event.activity matches activityFilter) {
61-
state.onActivityUpdated(event.activity)
62+
state.onActivityUpserted(event.activity)
6263
}
6364
}
6465

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,31 @@ internal class ActivityListStateImplTest {
5454
}
5555

5656
@Test
57-
fun `on onActivityUpdated, then update specific activity`() = runTest {
57+
fun `on onActivityUpserted, then update specific activity`() = runTest {
5858
val activity1 = activityData("activity-1")
5959
val activity2 = activityData("activity-2")
6060
setupInitialActivities(activity1, activity2)
6161

6262
val updatedActivity = activityData("activity-1", text = "Updated activity")
63-
activityListState.onActivityUpdated(updatedActivity)
63+
activityListState.onActivityUpserted(updatedActivity)
6464

6565
assertEquals(listOf(updatedActivity, activity2), activityListState.activities.value)
6666
}
6767

68+
@Test
69+
fun `on onActivityUpserted with new activity, then insert it in sorted position`() = runTest {
70+
val activity1 = activityData("activity-1", createdAt = 1000)
71+
val activity2 = activityData("activity-2", createdAt = 2000)
72+
setupInitialActivities(activity2, activity1)
73+
74+
val newActivity = activityData("activity-new", createdAt = 1500)
75+
activityListState.onActivityUpserted(newActivity)
76+
77+
// Default sort is by createdAt descending
78+
val expectedActivities = listOf(activity2, newActivity, activity1)
79+
assertEquals(expectedActivities, activityListState.activities.value)
80+
}
81+
6882
@Test
6983
fun `on onActivityRemoved, then remove specific activity`() = runTest {
7084
val activity1 = activityData("activity-1")

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,16 @@ internal class ActivityStateImplTest {
102102
assertNull(activityState.poll.value)
103103
}
104104

105+
@Test
106+
fun `on onActivityRemoved, then clear activity and poll state`() = runTest {
107+
setupInitialPoll(pollData())
108+
109+
activityState.onActivityRemoved()
110+
111+
assertNull(activityState.activity.value)
112+
assertNull(activityState.poll.value)
113+
}
114+
105115
@Test
106116
fun `on onActivityUpdated with new poll, then set new poll`() = runTest {
107117
val initialActivity = activityData("activity-1", text = "Original", poll = null)

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

Lines changed: 8 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -106,79 +106,33 @@ internal class FeedStateImplTest {
106106
}
107107

108108
@Test
109-
fun `on onActivityAdded, then add activity`() = runTest {
109+
fun `on onActivityUpserted, then add activity`() = runTest {
110110
val initialActivity = activityData()
111111
setupInitialState(listOf(initialActivity))
112112

113113
val newActivity = activityData("activity-2")
114-
feedState.onActivityAdded(newActivity)
114+
feedState.onActivityUpserted(newActivity)
115115

116116
val activities = feedState.activities.value
117117
assertEquals(listOf(initialActivity, newActivity), activities)
118118
}
119119

120120
@Test
121-
fun `on onActivityUpdated, then update activity`() = runTest {
121+
fun `on onActivityUpserted, then update activity`() = runTest {
122122
val initialActivity = activityData("activity-1")
123123
val activityPin = activityPin(initialActivity)
124124
setupInitialState(listOf(initialActivity), listOf(activityPin))
125125

126126
val updatedActivity = activityData("activity-1", text = "Updated activity")
127-
feedState.onActivityUpdated(updatedActivity)
127+
feedState.onActivityUpserted(updatedActivity)
128128

129129
assertEquals(listOf(updatedActivity), feedState.activities.value)
130130
val expectedPinnedActivity = activityPin.copy(activity = updatedActivity)
131131
assertEquals(listOf(expectedPinnedActivity), feedState.pinnedActivities.value)
132132
}
133133

134134
@Test
135-
fun `on onActivityUpdated, then preserve ownBookmarks when updating activity`() = runTest {
136-
val initialBookmark = bookmarkData("activity-1", currentUserId)
137-
val initialActivity =
138-
activityData("activity-1", text = "Original", ownBookmarks = listOf(initialBookmark))
139-
setupInitialState(listOf(initialActivity))
140-
141-
val updatedActivity =
142-
activityData("activity-1", text = "Updated", ownBookmarks = emptyList())
143-
feedState.onActivityUpdated(updatedActivity)
144-
145-
val expectedActivity = updatedActivity.copy(ownBookmarks = listOf(initialBookmark))
146-
assertEquals(listOf(expectedActivity), feedState.activities.value)
147-
}
148-
149-
@Test
150-
fun `on onActivityUpdated, then preserve ownReactions when updating activity`() = runTest {
151-
val initialReaction = feedsReactionData("activity-1", "like", currentUserId)
152-
val initialActivity =
153-
activityData("activity-1", text = "Original", ownReactions = listOf(initialReaction))
154-
setupInitialState(listOf(initialActivity))
155-
156-
val updatedActivity =
157-
activityData("activity-1", text = "Updated", ownReactions = emptyList())
158-
feedState.onActivityUpdated(updatedActivity)
159-
160-
val expectedActivity = updatedActivity.copy(ownReactions = listOf(initialReaction))
161-
assertEquals(listOf(expectedActivity), feedState.activities.value)
162-
}
163-
164-
@Test
165-
fun `on onActivityUpdated, then preserve poll ownVotes when updating activity`() = runTest {
166-
val ownVote = pollVoteData("vote-1", "poll-1", "option-1", currentUserId)
167-
val initialPoll = pollData("poll-1", "Test Poll", ownVotes = listOf(ownVote))
168-
val initialActivity = activityData("activity-1", text = "Original", poll = initialPoll)
169-
setupInitialState(listOf(initialActivity))
170-
171-
val updatedPoll = pollData("poll-1", "Updated Poll", ownVotes = emptyList())
172-
val updatedActivity = activityData("activity-1", text = "Updated", poll = updatedPoll)
173-
feedState.onActivityUpdated(updatedActivity)
174-
175-
val expectedPoll = updatedPoll.copy(ownVotes = listOf(ownVote))
176-
val expectedActivity = updatedActivity.copy(poll = expectedPoll)
177-
assertEquals(listOf(expectedActivity), feedState.activities.value)
178-
}
179-
180-
@Test
181-
fun `on onActivityUpdated, then preserve all own properties together in feed`() = runTest {
135+
fun `on onActivityUpserted, then preserve own properties in activity`() = runTest {
182136
val initialBookmark = bookmarkData("activity-1", currentUserId)
183137
val initialReaction = feedsReactionData("activity-1", "like", currentUserId)
184138
val ownVote = pollVoteData("vote-1", "poll-1", "option-1", currentUserId)
@@ -203,7 +157,7 @@ internal class FeedStateImplTest {
203157
ownBookmarks = emptyList(),
204158
ownReactions = emptyList(),
205159
)
206-
feedState.onActivityUpdated(updatedActivity)
160+
feedState.onActivityUpserted(updatedActivity)
207161

208162
// Verify all "own" properties are preserved
209163
val expectedPoll = updatedPoll.copy(ownVotes = listOf(ownVote))
@@ -217,13 +171,13 @@ internal class FeedStateImplTest {
217171
}
218172

219173
@Test
220-
fun `on onActivityUpdated with new poll in feed, then set new poll`() = runTest {
174+
fun `on onActivityUpserted with new poll in feed, then set new poll`() = runTest {
221175
val initialActivity = activityData("activity-1", text = "Original", poll = null)
222176
setupInitialState(listOf(initialActivity))
223177

224178
val newPoll = pollData("poll-1", "New Poll")
225179
val updatedActivity = activityData("activity-1", text = "Updated", poll = newPoll)
226-
feedState.onActivityUpdated(updatedActivity)
180+
feedState.onActivityUpserted(updatedActivity)
227181

228182
assertEquals(listOf(updatedActivity), feedState.activities.value)
229183
}

0 commit comments

Comments
 (0)