Skip to content

Conversation

@gpunto
Copy link
Contributor

@gpunto gpunto commented Nov 26, 2025

Goal

AND-818

Backend sends empty own_capabilities in the feed in activities on the activity added event (like for other "own" properties). So they added an endpoint to fetch them if we don't have them cached locally (e.g. we can cache them if feed.getOrCreate() was called because they're returned there).

Implementation

  • Create FeedsCapabilityRepository to handle caching and fetching capabilities
  • Create StateEventEnricher to use the repository's getOrRequest to enrich ActivityAdded or request capabilities to be fetched
  • Add FeedCapabilitiesUpdated state event to notify on capabilities cached & handle it where needed (i.e. where we also handle ActivityAdded)
  • Change ActivityData.update to preserve feed capabilities like other own properties

Testing

Checklist

  • Issue linked (if any)
  • Tests/docs updated
  • I have signed the Stream CLA (required for external contributors)

@gpunto gpunto requested a review from Copilot November 26, 2025 16:40
@gpunto gpunto added the pr:improvement Improvement label Nov 26, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Nov 26, 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.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 26, 2025

SDK Size Comparison 📏

SDK Before After Difference Status
stream-feeds-android-client 2.36 MB 2.37 MB 0.01 MB 🟢

Copilot finished reviewing on behalf of gpunto November 26, 2025 16:43
Copy link
Contributor

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 fetching feed own capabilities on ActivityAdded events when the capabilities aren't already cached. The backend sends empty own_capabilities in activities from WebSocket events, so this change introduces a batched API endpoint to fetch them on-demand when needed.

Key Changes:

  • Introduced FeedsCapabilityRepository with batching and caching to efficiently fetch and store feed capabilities
  • Created StateEventEnricher to enrich ActivityAdded events with cached capabilities or trigger fetch requests
  • Added FeedCapabilitiesUpdated state event to propagate capability updates across the system
  • Extended ActivityData.update() to preserve feed capabilities like other "own" properties (bookmarks, reactions, poll votes)

Reviewed changes

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

Show a summary per file
File Description
FeedsCapabilityRepository.kt New repository implementing batched fetching, caching, and distribution of feed capabilities with exponential retry logic
StateEventEnricher.kt New enricher that intercepts ActivityAdded events to populate capabilities from cache or trigger fetch requests
StateUpdateEvent.kt Added FeedCapabilitiesUpdated event to notify listeners when capabilities are cached
FeedEventHandler.kt Added handler for FeedCapabilitiesUpdated events in feed state
ActivityListEventHandler.kt Added handler for FeedCapabilitiesUpdated events in activity list state
FeedStateImpl.kt Implemented onFeedCapabilitiesUpdated() to update capabilities in activities matching the updated feed IDs
ActivityListStateImpl.kt Implemented onFeedCapabilitiesUpdated() to update capabilities in activities with matching feeds
FeedListImpl.kt Added capability caching when querying feeds
FeedImpl.kt Added capability caching on feed getOrCreate
ActivityListImpl.kt Added capability caching when querying activities
ActivityImpl.kt Added capability caching when fetching individual activities
ActivityOperations.kt Extended ActivityData.update() to preserve currentFeed capabilities; added updateFeedCapabilities() helper
FeedsClientImpl.kt Integrated StateEventEnricher into event processing pipeline; added repository and enricher dependencies
Create.kt Instantiated FeedsCapabilityRepository with batcher and StateEventEnricher; refactored subscription manager creation
*Test.kt files Comprehensive test coverage for new repository, enricher, event handlers, and state updates

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

@gpunto gpunto marked this pull request as ready for review November 27, 2025 11:05
@sonarqubecloud
Copy link

ownBookmarks = ownBookmarks,
ownReactions = ownReactions,
poll = updated.poll?.let { poll?.update(it) ?: it },
currentFeed = updated.currentFeed?.let { currentFeed?.update(it) ?: it },
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we fallback to this.currentFeed if updated.currentFeed == null? Otherwise we could end up overriding the this.currentFeed if the updated one is null?

import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CoroutineScope

internal class FeedsCapabilityRepository(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we rework this a bit so that we have an interface FeedsCapabilityRepository and a concrete implementation class FeedsCapabilityRepository to keep it aligned with the other repository definition?

val activity =
activitiesRepository.getActivity(activityId).onSuccess {
subscriptionManager.onEvent(StateUpdateEvent.ActivityUpdated(FidScope.unknown, it))
it.currentFeed?.let(capabilityRepository::cache)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it a valid scenario that we get updated ownCapabilities for the same feed?
I am thinking something along the way of:

  1. We do activity.get() -> get one set of ownCapabilities
  2. We do activityList.get() -> get a different set of ownCapabilities for the same feed

Wouldn't the activity from step one now reference the old capabilities set? I am thinking if we should maybe introduce FeedCapabilitiesUpdated handling for the Activity object as well 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:improvement Improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants