Skip to content

Conversation

gpunto
Copy link
Contributor

@gpunto gpunto commented Sep 1, 2025

Goal

Make sure that feeds that the client started watching, are re-watched on socket reconnection.

Implementation

Introduce a FeedWatchHandler class that keeps track of watched feeds and re-watch them when the socket state becomes connected

Testing

  • Unit tests covering the new code pass
  • Run the sample and cause a socket disconnection & reconnection. Then validate that the events are still received. E.g. disconnect because of network down & then reconnect by moving the app to background & foreground.

Copy link
Contributor

github-actions bot commented Sep 1, 2025

SDK Size Comparison 📏

SDK Before After Difference Status
stream-feeds-android-client 2.42 MB 2.42 MB 0.00 MB 🟢

@gpunto gpunto added the pr:new-feature New feature label Sep 1, 2025
@gpunto gpunto force-pushed the rewatch-on-reconnect branch from 4607cee to 4c84bae Compare September 1, 2025 12:35
@gpunto gpunto requested a review from Copilot September 1, 2025 12:38
Copilot

This comment was marked as outdated.

@gpunto gpunto force-pushed the rewatch-on-reconnect branch from 4c84bae to f85d0ef Compare September 1, 2025 12:40
@gpunto gpunto requested a review from Copilot September 1, 2025 12:40
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements feed rewatching functionality that automatically re-subscribes to feeds when the socket connection is re-established after a disconnection.

Key changes include:

  • Added FeedWatchHandler to track watched feeds and trigger re-subscription on reconnection
  • Updated FeedImpl to notify the watch handler when feeds start/stop being watched
  • Modified client creation and initialization to include the new watch handler

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
FeedWatchHandler.kt New handler class that tracks watched feeds and re-subscribes them on connection events
FeedWatchHandlerTest.kt Comprehensive test coverage for the new watch handler functionality
FeedImpl.kt Integration of watch handler notifications for start/stop watching operations
FeedImplTest.kt Updated tests to cover watch handler integration and added helper methods
FeedsClientImpl.kt Added watch handler dependency and connection recovery initialization
FeedsClientImplTest.kt Updated test setup to include the new watch handler dependency
Create.kt Added watch handler creation and wiring in the client factory

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@gpunto gpunto force-pushed the rewatch-on-reconnect branch 2 times, most recently from 9b4f9eb to 7566b65 Compare September 1, 2025 12:45
@gpunto gpunto marked this pull request as ready for review September 1, 2025 12:51
Copy link
Contributor

github-actions bot commented Sep 2, 2025

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled.

🎉 Great job! This PR is ready for review.

@gpunto gpunto force-pushed the rewatch-on-reconnect branch 3 times, most recently from eb13087 to fdc1801 Compare September 2, 2025 09:00
init {
scope.launch {
connectionState.filterIsInstance<StreamConnectionState.Connected>().collect {
rewatchAll()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rewatchAll() swallows the potential failure of the request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know but what's the alternative? I'm going to change it to queryFeeds as you suggested above, so we can easily return a Result, but we're still executing this code in background automatically, so clients won't receive anything anyway. Maybe we can log failures?

}
}

fun onStartWatching(feedId: FeedId) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:maybe startWatching(feedID) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put on because the watch handler is not really doing the watching, it's just being notified that we started watching (by executing the actual query)

watched[feedId] = Unit
}

fun onStopWatching(feedId: FeedId) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: same as the other stopWatching(feedId)

@@ -153,6 +155,7 @@ internal class FeedsClientImpl(
return Result.failure(IllegalArgumentException("Anonymous users cannot connect."))
}
coreClient.subscribe(clientSubscription)
connectionRecoveryHandler.start()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it better to just call rewatchAll here in case we have non empty list of watched feeds. Since if connect goes trough in the coreClient we will definitely have a new connection ID which will require re-watching??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually a change that is not directly tied to the rewatch logic. I discovered that we lost this call when migrating to the core client, so we were never reconnecting the socket automatically.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not concerned about connectionRecoveryHandler.start() it should be there.
My point is that right after that we can call rewatchAll instead of initing a collector in the watch handler.

* @property scope The [CoroutineScope] in which to launch coroutines for re-watching feeds.
*/
internal class FeedWatchHandler(
private val connectionState: Flow<StreamConnectionState>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Maybe its better for the FeedsWatchhandler to be dumb and just collect the bunch of feed IDs and wire to the public rewatchAll() : Result<Whatever>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To do that, we'd need to add a rewatchAll call everywhere we connect the core client (after we validated that the connection was successful), which atm would be in FeedsClientImpl and ConnectionRecoveryHandler. This means that if we ever add more code calling .connect(), we need to remember to also call rewatchAll. It's more error prone than using the socket state as the source of truth

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ConnectionRecoveryHandler I think should not call connect() directly to the core client, but that is another topic.

@gpunto gpunto force-pushed the rewatch-on-reconnect branch 3 times, most recently from 9491e16 to f8fbdb1 Compare September 3, 2025 07:35
@gpunto gpunto force-pushed the rewatch-on-reconnect branch from b5379b0 to 16375c8 Compare September 3, 2025 15:07
@gpunto gpunto force-pushed the rewatch-on-reconnect branch from 16375c8 to 37d171f Compare September 3, 2025 15:07
@@ -127,13 +129,17 @@ internal class FeedImpl(
get() = _state

override suspend fun getOrCreate(): Result<FeedData> {
if (query.watch) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am probably oversimplifying this whole logic, but wouldn't it be possible to handle the rewatching inside the FeedImpl class?
The FeedImpl already listens for WS events, so we could potentially react to ConnectedEvent and re-run the getOrCreate method (if watch = true). We would probably need to have some logic/guards for making sure that we call that only after a terminated + restored connection, but it would potentially reduce the complexity of the whole problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we receive ConnectedEvent, or at least I couldn't find it. Maybe it was there before integrating core?

We could still inject Flow<StreamConnectionState> and collect it inside FeedImpl, but I think we'd have a coordination problem in this scenario:

  1. Client creates two instances of the same feed, both having watch=true
  2. Client calls stopWatching on one of them -> so we stop receiving events
  3. The socket disconnects and reconnects
  4. At least one of the feeds won't know that the client called stopWatching, so it will call getOrCreate again and will resubscribe

I guess this can be solved by sharing some state across feed instances (i.e. the set of watched feeds), but at that point maybe it's better to leave the logic fully centralized? Not sure. 🤔 Maybe moving this responsibility into FeedImpl is still easier to understand vs the current approach?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Centralising this is better.

@aleksandar-apostolov
Copy link
Collaborator

LGTM (connection recovery will be handled in core at a later point)

@gpunto gpunto merged commit 22b9962 into develop Sep 10, 2025
6 checks passed
@gpunto gpunto deleted the rewatch-on-reconnect branch September 10, 2025 13:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr:new-feature New feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants