From 261bdc6d28d342e4c0f3f08b605abe851c06fd8c Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Fri, 19 Sep 2025 14:51:38 +0200 Subject: [PATCH 1/4] Migrate FeedList to consuming state update events --- .../client/internal/client/FeedsClientImpl.kt | 2 +- .../client/internal/state/FeedListImpl.kt | 4 +- .../internal/state/event/StateUpdateEvent.kt | 11 +++ .../event/handler/FeedListEventHandler.kt | 20 ++--- .../client/internal/state/FeedListImplTest.kt | 4 +- .../event/handler/BaseEventHandlerTest.kt | 49 +++++++++++ .../event/handler/FeedListEventHandlerTest.kt | 81 +++++++------------ 7 files changed, 106 insertions(+), 65 deletions(-) create mode 100644 stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BaseEventHandlerTest.kt diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt index f92506492..b3da3faa7 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt @@ -204,7 +204,7 @@ internal class FeedsClientImpl( FeedListImpl( query = query, feedsRepository = feedsRepository, - subscriptionManager = feedsEventsSubscriptionManager, + subscriptionManager = stateEventsSubscriptionManager, ) override fun followList(query: FollowsQuery): FollowList = diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedListImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedListImpl.kt index 1dfd16e21..041f91f9b 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedListImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedListImpl.kt @@ -23,7 +23,7 @@ import io.getstream.feeds.android.client.api.state.FeedListState import io.getstream.feeds.android.client.api.state.query.FeedsQuery import io.getstream.feeds.android.client.internal.repository.FeedsRepository import io.getstream.feeds.android.client.internal.state.event.handler.FeedListEventHandler -import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener /** * Represents a list of feeds with a query and state. @@ -36,7 +36,7 @@ import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener internal class FeedListImpl( override val query: FeedsQuery, private val feedsRepository: FeedsRepository, - private val subscriptionManager: StreamSubscriptionManager, + private val subscriptionManager: StreamSubscriptionManager, ) : FeedList { private val _state: FeedListStateImpl = FeedListStateImpl(query) diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/StateUpdateEvent.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/StateUpdateEvent.kt index 01ddb0bac..605943c8c 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/StateUpdateEvent.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/StateUpdateEvent.kt @@ -18,6 +18,7 @@ package io.getstream.feeds.android.client.internal.state.event import io.getstream.feeds.android.client.api.model.BookmarkData import io.getstream.feeds.android.client.api.model.BookmarkFolderData import io.getstream.feeds.android.client.api.model.CommentData +import io.getstream.feeds.android.client.api.model.FeedData import io.getstream.feeds.android.client.api.model.FeedsReactionData import io.getstream.feeds.android.client.api.model.toModel import io.getstream.feeds.android.network.models.BookmarkDeletedEvent @@ -29,6 +30,8 @@ import io.getstream.feeds.android.network.models.CommentDeletedEvent import io.getstream.feeds.android.network.models.CommentReactionAddedEvent import io.getstream.feeds.android.network.models.CommentReactionDeletedEvent import io.getstream.feeds.android.network.models.CommentUpdatedEvent +import io.getstream.feeds.android.network.models.FeedDeletedEvent +import io.getstream.feeds.android.network.models.FeedUpdatedEvent import io.getstream.feeds.android.network.models.WSEvent /** @@ -56,6 +59,10 @@ internal sealed interface StateUpdateEvent { data class CommentReactionDeleted(val comment: CommentData, val reaction: FeedsReactionData) : StateUpdateEvent + + data class FeedUpdated(val feed: FeedData) : StateUpdateEvent + + data class FeedDeleted(val fid: String) : StateUpdateEvent } internal fun WSEvent.toModel(): StateUpdateEvent? = @@ -81,5 +88,9 @@ internal fun WSEvent.toModel(): StateUpdateEvent? = is CommentReactionDeletedEvent -> StateUpdateEvent.CommentReactionDeleted(comment.toModel(), reaction.toModel()) + is FeedUpdatedEvent -> StateUpdateEvent.FeedUpdated(feed.toModel()) + + is FeedDeletedEvent -> StateUpdateEvent.FeedDeleted(fid) + else -> null } diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedListEventHandler.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedListEventHandler.kt index a0eab4add..344eabe76 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedListEventHandler.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedListEventHandler.kt @@ -15,24 +15,24 @@ */ package io.getstream.feeds.android.client.internal.state.event.handler -import io.getstream.feeds.android.client.api.model.toModel import io.getstream.feeds.android.client.internal.state.FeedListStateUpdates -import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener -import io.getstream.feeds.android.network.models.FeedDeletedEvent -import io.getstream.feeds.android.network.models.FeedUpdatedEvent -import io.getstream.feeds.android.network.models.WSEvent +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener -internal class FeedListEventHandler(private val state: FeedListStateUpdates) : FeedsEventListener { +internal class FeedListEventHandler(private val state: FeedListStateUpdates) : + StateUpdateEventListener { - override fun onEvent(event: WSEvent) { + override fun onEvent(event: StateUpdateEvent) { when (event) { - is FeedUpdatedEvent -> { - state.onFeedUpdated(event.feed.toModel()) + is StateUpdateEvent.FeedUpdated -> { + state.onFeedUpdated(event.feed) } - is FeedDeletedEvent -> { + is StateUpdateEvent.FeedDeleted -> { state.onFeedRemoved(event.fid) } + + else -> {} } } } diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/FeedListImplTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/FeedListImplTest.kt index 818f4a5a9..4b411dcf8 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/FeedListImplTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/FeedListImplTest.kt @@ -21,7 +21,7 @@ import io.getstream.feeds.android.client.api.model.PaginationData import io.getstream.feeds.android.client.api.model.PaginationResult import io.getstream.feeds.android.client.api.state.query.FeedsQuery import io.getstream.feeds.android.client.internal.repository.FeedsRepository -import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener import io.getstream.feeds.android.client.internal.test.TestData.feedData import io.mockk.coEvery import io.mockk.coVerify @@ -32,7 +32,7 @@ import org.junit.Test internal class FeedListImplTest { private val feedsRepository: FeedsRepository = mockk() - private val subscriptionManager: StreamSubscriptionManager = + private val subscriptionManager: StreamSubscriptionManager = mockk(relaxed = true) private val query = FeedsQuery(limit = 10, watch = false) diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BaseEventHandlerTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BaseEventHandlerTest.kt new file mode 100644 index 000000000..42dfbff44 --- /dev/null +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BaseEventHandlerTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014-2025 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-feeds-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getstream.feeds.android.client.internal.state.event.handler + +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener +import io.mockk.MockKVerificationScope +import io.mockk.verify +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +internal abstract class BaseEventHandlerTest( + protected val testName: String, + protected val event: StateUpdateEvent, + protected val verifyBlock: MockKVerificationScope.(State) -> Unit, +) { + protected abstract val state: State + + protected abstract val handler: StateUpdateEventListener + + @Test + internal fun `handle event`() { + handler.onEvent(event) + verify { verifyBlock(state) } + } + + companion object { + fun testParams( + name: String, + event: StateUpdateEvent, + verifyBlock: MockKVerificationScope.(S) -> Unit, + ): Array = arrayOf(name, event, verifyBlock) + } +} diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedListEventHandlerTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedListEventHandlerTest.kt index e471649c9..bb5fd4eb2 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedListEventHandlerTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedListEventHandlerTest.kt @@ -15,58 +15,39 @@ */ package io.getstream.feeds.android.client.internal.state.event.handler -import io.getstream.feeds.android.client.api.model.toModel import io.getstream.feeds.android.client.internal.state.FeedListStateUpdates -import io.getstream.feeds.android.client.internal.test.TestData.feedResponse -import io.getstream.feeds.android.network.models.FeedDeletedEvent -import io.getstream.feeds.android.network.models.FeedUpdatedEvent -import io.getstream.feeds.android.network.models.WSEvent -import io.mockk.called +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent.FeedDeleted +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent.FeedUpdated +import io.getstream.feeds.android.client.internal.test.TestData.feedData +import io.mockk.MockKVerificationScope import io.mockk.mockk -import io.mockk.verify -import java.util.Date -import org.junit.Test - -internal class FeedListEventHandlerTest { - private val state: FeedListStateUpdates = mockk(relaxed = true) - - private val handler = FeedListEventHandler(state) - - @Test - fun `on FeedUpdatedEvent, then call onFeedUpdated`() { - val feed = feedResponse() - val event = - FeedUpdatedEvent( - createdAt = Date(), - fid = "user:feed-1", - feed = feed, - type = "feeds.feed.updated", +import org.junit.runners.Parameterized + +internal class FeedListEventHandlerTest( + testName: String, + event: StateUpdateEvent, + verifyBlock: MockKVerificationScope.(FeedListStateUpdates) -> Unit, +) : BaseEventHandlerTest(testName, event, verifyBlock) { + + override val state: FeedListStateUpdates = mockk(relaxed = true) + override val handler = FeedListEventHandler(state) + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun data(): List> = + listOf( + testParams( + name = "FeedUpdated calls onFeedUpdated", + event = FeedUpdated(feedData()), + verifyBlock = { state -> state.onFeedUpdated(feedData()) }, + ), + testParams( + name = "FeedDeleted calls onFeedRemoved", + event = FeedDeleted("user:feed-1"), + verifyBlock = { state -> state.onFeedRemoved("user:feed-1") }, + ), ) - - handler.onEvent(event) - - verify { state.onFeedUpdated(feed.toModel()) } - } - - @Test - fun `on FeedDeletedEvent, then call onFeedRemoved`() { - val event = - FeedDeletedEvent(createdAt = Date(), fid = "user:feed-1", type = "feeds.feed.deleted") - - handler.onEvent(event) - - verify { state.onFeedRemoved("user:feed-1") } - } - - @Test - fun `on unknown event, then do nothing`() { - val unknownEvent = - object : WSEvent { - override fun getWSEventType(): String = "unknown.event" - } - - handler.onEvent(unknownEvent) - - verify { state wasNot called } } } From a79d8d5900092e860c14b1af77ad9c5d7a904dc0 Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:04:19 +0200 Subject: [PATCH 2/4] Migrate ActivityReactionList to consuming state update events --- .../client/internal/client/FeedsClientImpl.kt | 2 +- .../state/ActivityReactionListImpl.kt | 4 +- .../internal/state/event/StateUpdateEvent.kt | 11 +++ .../ActivityReactionListEventHandler.kt | 25 +++---- .../state/ActivityReactionListImplTest.kt | 4 +- .../ActivityReactionListEventHandlerTest.kt | 74 +++++-------------- 6 files changed, 45 insertions(+), 75 deletions(-) diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt index b3da3faa7..8a8855bb6 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt @@ -250,7 +250,7 @@ internal class FeedsClientImpl( ActivityReactionListImpl( query = query, activitiesRepository = activitiesRepository, - subscriptionManager = feedsEventsSubscriptionManager, + subscriptionManager = stateEventsSubscriptionManager, ) override suspend fun addActivity(request: AddActivityRequest): Result { diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/ActivityReactionListImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/ActivityReactionListImpl.kt index be86aa4ad..96482b41f 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/ActivityReactionListImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/ActivityReactionListImpl.kt @@ -24,7 +24,7 @@ import io.getstream.feeds.android.client.api.state.query.ActivityReactionsQuery import io.getstream.feeds.android.client.api.state.query.toRequest import io.getstream.feeds.android.client.internal.repository.ActivitiesRepository import io.getstream.feeds.android.client.internal.state.event.handler.ActivityReactionListEventHandler -import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener /** * A list of activity reactions that provides pagination, filtering, and real-time updates. @@ -42,7 +42,7 @@ import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener internal class ActivityReactionListImpl( override val query: ActivityReactionsQuery, private val activitiesRepository: ActivitiesRepository, - private val subscriptionManager: StreamSubscriptionManager, + private val subscriptionManager: StreamSubscriptionManager, ) : ActivityReactionList { private val _state = ActivityReactionListStateImpl(query) diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/StateUpdateEvent.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/StateUpdateEvent.kt index 605943c8c..d90771d90 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/StateUpdateEvent.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/StateUpdateEvent.kt @@ -21,6 +21,8 @@ import io.getstream.feeds.android.client.api.model.CommentData import io.getstream.feeds.android.client.api.model.FeedData import io.getstream.feeds.android.client.api.model.FeedsReactionData import io.getstream.feeds.android.client.api.model.toModel +import io.getstream.feeds.android.network.models.ActivityReactionAddedEvent +import io.getstream.feeds.android.network.models.ActivityReactionDeletedEvent import io.getstream.feeds.android.network.models.BookmarkDeletedEvent import io.getstream.feeds.android.network.models.BookmarkFolderDeletedEvent import io.getstream.feeds.android.network.models.BookmarkFolderUpdatedEvent @@ -40,6 +42,10 @@ import io.getstream.feeds.android.network.models.WSEvent */ internal sealed interface StateUpdateEvent { + data class ActivityReactionAdded(val reaction: FeedsReactionData) : StateUpdateEvent + + data class ActivityReactionDeleted(val reaction: FeedsReactionData) : StateUpdateEvent + data class BookmarkDeleted(val bookmark: BookmarkData) : StateUpdateEvent data class BookmarkUpdated(val bookmark: BookmarkData) : StateUpdateEvent @@ -67,6 +73,11 @@ internal sealed interface StateUpdateEvent { internal fun WSEvent.toModel(): StateUpdateEvent? = when (this) { + is ActivityReactionAddedEvent -> StateUpdateEvent.ActivityReactionAdded(reaction.toModel()) + + is ActivityReactionDeletedEvent -> + StateUpdateEvent.ActivityReactionDeleted(reaction.toModel()) + is BookmarkDeletedEvent -> StateUpdateEvent.BookmarkDeleted(bookmark.toModel()) is BookmarkUpdatedEvent -> StateUpdateEvent.BookmarkUpdated(bookmark.toModel()) diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityReactionListEventHandler.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityReactionListEventHandler.kt index 6f0998e98..4c1545928 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityReactionListEventHandler.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityReactionListEventHandler.kt @@ -15,31 +15,30 @@ */ package io.getstream.feeds.android.client.internal.state.event.handler -import io.getstream.feeds.android.client.api.model.toModel import io.getstream.feeds.android.client.internal.state.ActivityReactionListStateUpdates -import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener -import io.getstream.feeds.android.network.models.ActivityReactionAddedEvent -import io.getstream.feeds.android.network.models.ActivityReactionDeletedEvent -import io.getstream.feeds.android.network.models.WSEvent +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener internal class ActivityReactionListEventHandler( private val activityId: String, private val state: ActivityReactionListStateUpdates, -) : FeedsEventListener { +) : StateUpdateEventListener { - override fun onEvent(event: WSEvent) { + override fun onEvent(event: StateUpdateEvent) { when (event) { - is ActivityReactionAddedEvent -> { - if (event.activity.id == activityId) { - state.onReactionAdded(event.reaction.toModel()) + is StateUpdateEvent.ActivityReactionAdded -> { + if (event.reaction.activityId == activityId) { + state.onReactionAdded(event.reaction) } } - is ActivityReactionDeletedEvent -> { - if (event.activity.id == activityId) { - state.onReactionRemoved(event.reaction.toModel()) + is StateUpdateEvent.ActivityReactionDeleted -> { + if (event.reaction.activityId == activityId) { + state.onReactionRemoved(event.reaction) } } + + else -> {} } } } diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/ActivityReactionListImplTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/ActivityReactionListImplTest.kt index 0a3435b80..1d62e963e 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/ActivityReactionListImplTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/ActivityReactionListImplTest.kt @@ -21,7 +21,7 @@ import io.getstream.feeds.android.client.api.model.PaginationData import io.getstream.feeds.android.client.api.model.PaginationResult import io.getstream.feeds.android.client.api.state.query.ActivityReactionsQuery import io.getstream.feeds.android.client.internal.repository.ActivitiesRepository -import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener import io.getstream.feeds.android.client.internal.test.TestData.feedsReactionData import io.mockk.coEvery import io.mockk.coVerify @@ -32,7 +32,7 @@ import org.junit.Test internal class ActivityReactionListImplTest { private val activitiesRepository: ActivitiesRepository = mockk() - private val subscriptionManager: StreamSubscriptionManager = + private val subscriptionManager: StreamSubscriptionManager = mockk(relaxed = true) private val query = ActivityReactionsQuery(activityId = "activity-1", limit = 10) diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityReactionListEventHandlerTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityReactionListEventHandlerTest.kt index 6b50889af..944250e99 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityReactionListEventHandlerTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityReactionListEventHandlerTest.kt @@ -15,17 +15,12 @@ */ package io.getstream.feeds.android.client.internal.state.event.handler -import io.getstream.feeds.android.client.api.model.toModel import io.getstream.feeds.android.client.internal.state.ActivityReactionListStateUpdates -import io.getstream.feeds.android.client.internal.test.TestData.activityResponse -import io.getstream.feeds.android.client.internal.test.TestData.feedsReactionResponse -import io.getstream.feeds.android.network.models.ActivityReactionAddedEvent -import io.getstream.feeds.android.network.models.ActivityReactionDeletedEvent -import io.getstream.feeds.android.network.models.WSEvent +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent +import io.getstream.feeds.android.client.internal.test.TestData.feedsReactionData import io.mockk.called import io.mockk.mockk import io.mockk.verify -import java.util.Date import org.junit.Test internal class ActivityReactionListEventHandlerTest { @@ -35,35 +30,19 @@ internal class ActivityReactionListEventHandlerTest { private val handler = ActivityReactionListEventHandler(activityId, state) @Test - fun `on ActivityReactionAddedEvent for matching activity, then call onReactionAdded`() { - val activity = activityResponse(activityId) - val reaction = feedsReactionResponse() - val event = - ActivityReactionAddedEvent( - createdAt = Date(), - fid = "user:feed-1", - activity = activity, - reaction = reaction, - type = "feeds.activity.reaction.added", - ) + fun `on ActivityReactionAdded for matching activity, then call onReactionAdded`() { + val reaction = feedsReactionData(activityId) + val event = StateUpdateEvent.ActivityReactionAdded(reaction) handler.onEvent(event) - verify { state.onReactionAdded(reaction.toModel()) } + verify { state.onReactionAdded(reaction) } } @Test - fun `on ActivityReactionAddedEvent for different activity, then do not call onReactionAdded`() { - val activity = activityResponse("different-activity") - val reaction = feedsReactionResponse() - val event = - ActivityReactionAddedEvent( - createdAt = Date(), - fid = "user:feed-1", - activity = activity, - reaction = reaction, - type = "feeds.activity.reaction.added", - ) + fun `on ActivityReactionAdded for different activity, then do not call onReactionAdded`() { + val reaction = feedsReactionData("different-activity") + val event = StateUpdateEvent.ActivityReactionAdded(reaction) handler.onEvent(event) @@ -71,35 +50,19 @@ internal class ActivityReactionListEventHandlerTest { } @Test - fun `on ActivityReactionDeletedEvent for matching activity, then call onReactionRemoved`() { - val activity = activityResponse(activityId) - val reaction = feedsReactionResponse() - val event = - ActivityReactionDeletedEvent( - createdAt = Date(), - fid = "user:feed-1", - activity = activity, - reaction = reaction, - type = "feeds.activity.reaction.deleted", - ) + fun `on ActivityReactionDeleted for matching activity, then call onReactionRemoved`() { + val reaction = feedsReactionData(activityId) + val event = StateUpdateEvent.ActivityReactionDeleted(reaction) handler.onEvent(event) - verify { state.onReactionRemoved(reaction.toModel()) } + verify { state.onReactionRemoved(reaction) } } @Test - fun `on ActivityReactionDeletedEvent for different activity, then do not call onReactionRemoved`() { - val activity = activityResponse("different-activity") - val reaction = feedsReactionResponse() - val event = - ActivityReactionDeletedEvent( - createdAt = Date(), - fid = "user:feed-1", - activity = activity, - reaction = reaction, - type = "feeds.activity.reaction.deleted", - ) + fun `on ActivityReactionDeleted for different activity, then do not call onReactionRemoved`() { + val reaction = feedsReactionData("different-activity") + val event = StateUpdateEvent.ActivityReactionDeleted(reaction) handler.onEvent(event) @@ -108,10 +71,7 @@ internal class ActivityReactionListEventHandlerTest { @Test fun `on unknown event, then do nothing`() { - val unknownEvent = - object : WSEvent { - override fun getWSEventType(): String = "unknown.event" - } + val unknownEvent = StateUpdateEvent.BookmarkFolderDeleted("folder-id") handler.onEvent(unknownEvent) From f602f1613a7bcbe7bc3feeca441a495fc1f0303e Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:21:26 +0200 Subject: [PATCH 3/4] Migrate CommentReactionList to consuming state update event --- .../client/internal/client/FeedsClientImpl.kt | 2 +- .../internal/state/CommentReactionListImpl.kt | 4 +-- .../CommentReactionListEventHandler.kt | 13 ++++---- .../state/CommentReactionListImplTest.kt | 4 +-- .../CommentReactionListEventHandlerTest.kt | 31 ++++++------------- 5 files changed, 20 insertions(+), 34 deletions(-) diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt index 8a8855bb6..02d3ef2ea 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt @@ -310,7 +310,7 @@ internal class FeedsClientImpl( CommentReactionListImpl( query = query, commentsRepository = commentsRepository, - subscriptionManager = feedsEventsSubscriptionManager, + subscriptionManager = stateEventsSubscriptionManager, ) override fun memberList(query: MembersQuery): MemberList = diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/CommentReactionListImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/CommentReactionListImpl.kt index 097f6381d..26c613e79 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/CommentReactionListImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/CommentReactionListImpl.kt @@ -23,7 +23,7 @@ import io.getstream.feeds.android.client.api.state.CommentReactionListState import io.getstream.feeds.android.client.api.state.query.CommentReactionsQuery import io.getstream.feeds.android.client.internal.repository.CommentsRepository import io.getstream.feeds.android.client.internal.state.event.handler.CommentReactionListEventHandler -import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener /** * A class representing a paginated list of reactions for a specific comment. @@ -40,7 +40,7 @@ import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener internal class CommentReactionListImpl( override val query: CommentReactionsQuery, private val commentsRepository: CommentsRepository, - private val subscriptionManager: StreamSubscriptionManager, + private val subscriptionManager: StreamSubscriptionManager, ) : CommentReactionList { private val _state: CommentReactionListStateImpl = CommentReactionListStateImpl(query) diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/CommentReactionListEventHandler.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/CommentReactionListEventHandler.kt index 572f7ea43..ab65937a8 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/CommentReactionListEventHandler.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/CommentReactionListEventHandler.kt @@ -15,17 +15,16 @@ */ package io.getstream.feeds.android.client.internal.state.event.handler -import io.getstream.feeds.android.client.api.model.toModel import io.getstream.feeds.android.client.internal.state.CommentReactionListStateUpdates -import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener -import io.getstream.feeds.android.network.models.CommentReactionDeletedEvent -import io.getstream.feeds.android.network.models.WSEvent +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener internal class CommentReactionListEventHandler(private val state: CommentReactionListStateUpdates) : - FeedsEventListener { - override fun onEvent(event: WSEvent) { + StateUpdateEventListener { + override fun onEvent(event: StateUpdateEvent) { when (event) { - is CommentReactionDeletedEvent -> state.onReactionRemoved(event.reaction.toModel()) + is StateUpdateEvent.CommentReactionDeleted -> state.onReactionRemoved(event.reaction) + else -> {} } } } diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/CommentReactionListImplTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/CommentReactionListImplTest.kt index 1116e4541..7c49e734a 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/CommentReactionListImplTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/CommentReactionListImplTest.kt @@ -21,7 +21,7 @@ import io.getstream.feeds.android.client.api.model.PaginationData import io.getstream.feeds.android.client.api.model.PaginationResult import io.getstream.feeds.android.client.api.state.query.CommentReactionsQuery import io.getstream.feeds.android.client.internal.repository.CommentsRepository -import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener import io.getstream.feeds.android.client.internal.test.TestData.feedsReactionData import io.mockk.coEvery import io.mockk.coVerify @@ -32,7 +32,7 @@ import org.junit.Test internal class CommentReactionListImplTest { private val commentsRepository: CommentsRepository = mockk() - private val subscriptionManager: StreamSubscriptionManager = + private val subscriptionManager: StreamSubscriptionManager = mockk(relaxed = true) private val query = CommentReactionsQuery(commentId = "comment-1", limit = 10) diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/CommentReactionListEventHandlerTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/CommentReactionListEventHandlerTest.kt index 69f16eb28..acb453ffe 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/CommentReactionListEventHandlerTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/CommentReactionListEventHandlerTest.kt @@ -15,16 +15,13 @@ */ package io.getstream.feeds.android.client.internal.state.event.handler -import io.getstream.feeds.android.client.api.model.toModel import io.getstream.feeds.android.client.internal.state.CommentReactionListStateUpdates -import io.getstream.feeds.android.client.internal.test.TestData.commentResponse -import io.getstream.feeds.android.client.internal.test.TestData.feedsReactionResponse -import io.getstream.feeds.android.network.models.CommentReactionDeletedEvent -import io.getstream.feeds.android.network.models.WSEvent +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent +import io.getstream.feeds.android.client.internal.test.TestData.commentData +import io.getstream.feeds.android.client.internal.test.TestData.feedsReactionData import io.mockk.called import io.mockk.mockk import io.mockk.verify -import java.util.Date import org.junit.Test internal class CommentReactionListEventHandlerTest { @@ -33,29 +30,19 @@ internal class CommentReactionListEventHandlerTest { private val handler = CommentReactionListEventHandler(state) @Test - fun `on CommentReactionDeletedEvent, then call onReactionRemoved`() { - val reaction = feedsReactionResponse() - val comment = commentResponse() - val event = - CommentReactionDeletedEvent( - createdAt = Date(), - fid = "user:feed-1", - comment = comment, - reaction = reaction, - type = "feeds.comment.reaction.deleted", - ) + fun `on CommentReactionDeleted, then call onReactionRemoved`() { + val reaction = feedsReactionData() + val comment = commentData() + val event = StateUpdateEvent.CommentReactionDeleted(comment, reaction) handler.onEvent(event) - verify { state.onReactionRemoved(reaction.toModel()) } + verify { state.onReactionRemoved(reaction) } } @Test fun `on unknown event, then do nothing`() { - val unknownEvent = - object : WSEvent { - override fun getWSEventType(): String = "unknown.event" - } + val unknownEvent = StateUpdateEvent.BookmarkFolderDeleted("folder-id") handler.onEvent(unknownEvent) From 4b598023960b273f0c5aaca0c735848ccc392171 Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:35:47 +0200 Subject: [PATCH 4/4] Migrate BookmarkFolderList to consuming state update event --- .../client/internal/client/FeedsClientImpl.kt | 2 +- .../internal/state/BookmarkFolderListImpl.kt | 4 +- .../handler/BookmarkFolderListEventHandler.kt | 18 ++-- .../state/BookmarkFolderListImplTest.kt | 4 +- .../BookmarkFolderListEventHandlerTest.kt | 85 +++++++------------ 5 files changed, 44 insertions(+), 69 deletions(-) diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt index 02d3ef2ea..f5ad1e5c6 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt @@ -280,7 +280,7 @@ internal class FeedsClientImpl( BookmarkFolderListImpl( query = query, bookmarksRepository = bookmarksRepository, - subscriptionManager = feedsEventsSubscriptionManager, + subscriptionManager = stateEventsSubscriptionManager, ) override fun commentList(query: CommentsQuery): CommentList = diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkFolderListImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkFolderListImpl.kt index 1945394b7..53b3fce48 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkFolderListImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkFolderListImpl.kt @@ -23,7 +23,7 @@ import io.getstream.feeds.android.client.api.state.BookmarkFolderListState import io.getstream.feeds.android.client.api.state.query.BookmarkFoldersQuery import io.getstream.feeds.android.client.internal.repository.BookmarksRepository import io.getstream.feeds.android.client.internal.state.event.handler.BookmarkFolderListEventHandler -import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener /** * A class that manages a paginated list of bookmark folders. @@ -40,7 +40,7 @@ import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener internal class BookmarkFolderListImpl( override val query: BookmarkFoldersQuery, private val bookmarksRepository: BookmarksRepository, - private val subscriptionManager: StreamSubscriptionManager, + private val subscriptionManager: StreamSubscriptionManager, ) : BookmarkFolderList { private val _state: BookmarkFolderListStateImpl = BookmarkFolderListStateImpl(query) diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkFolderListEventHandler.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkFolderListEventHandler.kt index a5dc03629..15f9864e4 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkFolderListEventHandler.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkFolderListEventHandler.kt @@ -15,21 +15,19 @@ */ package io.getstream.feeds.android.client.internal.state.event.handler -import io.getstream.feeds.android.client.api.model.toModel import io.getstream.feeds.android.client.internal.state.BookmarkFolderListStateUpdates -import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener -import io.getstream.feeds.android.network.models.BookmarkFolderDeletedEvent -import io.getstream.feeds.android.network.models.BookmarkFolderUpdatedEvent -import io.getstream.feeds.android.network.models.WSEvent +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener internal class BookmarkFolderListEventHandler(private val state: BookmarkFolderListStateUpdates) : - FeedsEventListener { + StateUpdateEventListener { - override fun onEvent(event: WSEvent) { + override fun onEvent(event: StateUpdateEvent) { when (event) { - is BookmarkFolderDeletedEvent -> state.onBookmarkFolderRemoved(event.bookmarkFolder.id) - is BookmarkFolderUpdatedEvent -> - state.onBookmarkFolderUpdated(event.bookmarkFolder.toModel()) + is StateUpdateEvent.BookmarkFolderDeleted -> + state.onBookmarkFolderRemoved(event.folderId) + is StateUpdateEvent.BookmarkFolderUpdated -> state.onBookmarkFolderUpdated(event.folder) + else -> {} } } } diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkFolderListImplTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkFolderListImplTest.kt index da402acef..3b28fa3ab 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkFolderListImplTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkFolderListImplTest.kt @@ -21,7 +21,7 @@ import io.getstream.feeds.android.client.api.model.PaginationData import io.getstream.feeds.android.client.api.model.PaginationResult import io.getstream.feeds.android.client.api.state.query.BookmarkFoldersQuery import io.getstream.feeds.android.client.internal.repository.BookmarksRepository -import io.getstream.feeds.android.client.internal.subscribe.FeedsEventListener +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener import io.getstream.feeds.android.client.internal.test.TestData.bookmarkFolderData import io.mockk.coEvery import io.mockk.coVerify @@ -32,7 +32,7 @@ import org.junit.Test internal class BookmarkFolderListImplTest { private val bookmarksRepository: BookmarksRepository = mockk() - private val subscriptionManager: StreamSubscriptionManager = + private val subscriptionManager: StreamSubscriptionManager = mockk(relaxed = true) private val query = BookmarkFoldersQuery(limit = 10) diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkFolderListEventHandlerTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkFolderListEventHandlerTest.kt index 4314b530f..44574e1bf 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkFolderListEventHandlerTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkFolderListEventHandlerTest.kt @@ -15,62 +15,39 @@ */ package io.getstream.feeds.android.client.internal.state.event.handler -import io.getstream.feeds.android.client.api.model.toModel import io.getstream.feeds.android.client.internal.state.BookmarkFolderListStateUpdates -import io.getstream.feeds.android.client.internal.test.TestData.bookmarkFolderResponse -import io.getstream.feeds.android.network.models.BookmarkFolderDeletedEvent -import io.getstream.feeds.android.network.models.BookmarkFolderUpdatedEvent -import io.getstream.feeds.android.network.models.WSEvent -import io.mockk.called +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent.BookmarkFolderDeleted +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent.BookmarkFolderUpdated +import io.getstream.feeds.android.client.internal.test.TestData.bookmarkFolderData +import io.mockk.MockKVerificationScope import io.mockk.mockk -import io.mockk.verify -import java.util.Date -import org.junit.Test - -internal class BookmarkFolderListEventHandlerTest { - private val state: BookmarkFolderListStateUpdates = mockk(relaxed = true) - - private val handler = BookmarkFolderListEventHandler(state) - - @Test - fun `on BookmarkFolderDeletedEvent, then call onBookmarkFolderRemoved`() { - val bookmarkFolder = bookmarkFolderResponse() - val event = - BookmarkFolderDeletedEvent( - createdAt = Date(), - bookmarkFolder = bookmarkFolder, - type = "feeds.bookmark_folder.deleted", - ) - - handler.onEvent(event) - - verify { state.onBookmarkFolderRemoved(bookmarkFolder.id) } - } - - @Test - fun `on BookmarkFolderUpdatedEvent, then call onBookmarkFolderUpdated`() { - val bookmarkFolder = bookmarkFolderResponse() - val event = - BookmarkFolderUpdatedEvent( - createdAt = Date(), - bookmarkFolder = bookmarkFolder, - type = "feeds.bookmark_folder.updated", +import org.junit.runners.Parameterized + +internal class BookmarkFolderListEventHandlerTest( + testName: String, + event: StateUpdateEvent, + verifyBlock: MockKVerificationScope.(BookmarkFolderListStateUpdates) -> Unit, +) : BaseEventHandlerTest(testName, event, verifyBlock) { + + override val state: BookmarkFolderListStateUpdates = mockk(relaxed = true) + override val handler = BookmarkFolderListEventHandler(state) + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun data(): List> = + listOf( + testParams( + name = "BookmarkFolderDeleted calls onBookmarkFolderRemoved", + event = BookmarkFolderDeleted("folder-123"), + verifyBlock = { state -> state.onBookmarkFolderRemoved("folder-123") }, + ), + testParams( + name = "BookmarkFolderUpdated calls onBookmarkFolderUpdated", + event = BookmarkFolderUpdated(bookmarkFolderData()), + verifyBlock = { state -> state.onBookmarkFolderUpdated(bookmarkFolderData()) }, + ), ) - - handler.onEvent(event) - - verify { state.onBookmarkFolderUpdated(bookmarkFolder.toModel()) } - } - - @Test - fun `on unknown event, then do nothing`() { - val unknownEvent = - object : WSEvent { - override fun getWSEventType(): String = "unknown.event" - } - - handler.onEvent(unknownEvent) - - verify { state wasNot called } } }