Activity Feeds V3 is in closed alpha — do not use it in production (just yet).

Intro & Defaults

Creating Feed Groups & Defaults

There are several feed groups setup by default.

GroupDescription
userA feed setup for the content a user creates. Typically you add activities here when someone writes a post
timelineThe timeline feed is used when you’re following. So if user Charlie is following John, timeline:charlie would follow user:john
foryouA version of the timeline feed that adds popular content, and priorities popularity over recency
notificationA notification feed. Think of the bell icon you see in most apps

You can also create your own feed group. Here’s how to create a feed group using the API:

// Note: Feed group management is typically done server-side
// This is an example of the configuration structure
let feedGroupConfig: [String: Any] = [
    "id": "myid",
    "activity_processors": [["type": "topic"]], // activity processors do post processing on activities. AI topic analysis, etc.
    "activity_selectors": [["type": "following"]], // control how activities end up in this feed. direct, following, search, querying etc.
    "ranking": ["type": "time"], // how activities are ranked
    "aggregation": ["type": "count"], // group activities together
    "notification": ["type": "count"], // keep track of seen and read state on notification feeds
    "custom": ["description": "My custom feed group"] // anything else to store on the feed group
]

For You Feeds

Many apps want to have a “for you” or personalized feed. There are a couple benefits to a personalized feed:

  • Works well even if your users don’t spend much time setting up follows
  • Can be a mechanism to discover new content or things to follow

Stream offers a few built-in methods to create a for you feed and gives you the API access to do more advanced customization if needed.

Activity Ranking

When ranking activities you can specify a ranking formula. This can combine an age decay with popularity and other factors.

// Note: This is typically configured server-side
let rankingConfig = [
    "type": "time",
    "score": "decay_linear(time) * popularity"
]

let feedGroupConfig: [String: Any] = [
    "id": "timeline",
    "ranking": rankingConfig
]

Interest Based “For You” Style Feed

This next example is a bit more complicated. It uses an activity processor to add topic data to activities and ranks content you’re likely to engage with higher in the feed.

// Note: This is typically configured server-side

// Run the activity processors to analyse topics for text & images
let imageProcessor = ["type": "image_topic"]
let textProcessor = ["type": "text_topic"]
let userFeedConfig: [String: Any] = [
    "id": "user",
    "activity_processors": [imageProcessor, textProcessor]
]

// Activity selectors change which activities are included in the feed
// The default "following" selectors gets activities from the feeds you follow
// The "popular" activity selectors includes the popular activities
// And "interest" activities similar to activities you've engaged with in the past
// You can use multiple selectors in 1 feed
let activitySelectors = [
    ["type": "popular"],
    ["type": "following"],
    ["type": "interest"]
]

// Rank for a user based on interest score
// This calculates a score 0-1.0 of how well the activity matches the user's prior interest
let rankingConfig = [
    "type": "interest",
    "score": "decay_linear(time) * interest * decay_linear(popularity)"
]

let timelineConfig: [String: Any] = [
    "id": "timeline",
    "ranking": rankingConfig,
    "activity_selectors": activitySelectors
]

// Read the feed
// Activities will include following, popular and similar (via interest) activities
// Sorted by time decay, interest and popularity
let forYouFeed = client.feed(group: "timeline", id: "thierry")
let response = try await forYouFeed.getOrCreate()

Additional Ranking

There are a few ways that you can extend the ranking in feeds. This allows you pass in any insights you have about the user.

// Pass external ranking data when reading the feed
let query = FeedQuery(
    group: "timeline",
    id: "thierry",
    externalRanking: [
        "user_engagement_score": 0.8,
        "content_preference": 0.9,
        "time_of_day_bonus": 1.2
    ]
)
let feed = client.feed(for: query)
let feedData = try await feed.getOrCreate()

You can also rank on custom data in the activity:

let activity = try await feed.addActivity(
    request: .init(
        custom: [
            "quality_score": 0.95,
            "engagement_prediction": 0.8
        ],
        text: "Great content",
        type: "post"
    )
)

Ranking

The ranking system allows you to customize how activities are ordered in your feeds. Here are some common ranking strategies:

Time-based Ranking

// Simple time-based ranking (newest first)
let timeRanking = [
    "type": "time",
    "direction": "desc"
]

Popularity-based Ranking

// Rank by popularity (likes, comments, shares)
let popularityRanking = [
    "type": "popularity",
    "score": "likes + comments * 2 + shares * 3"
]

Hybrid Ranking

// Combine time decay with popularity
let hybridRanking = [
    "type": "hybrid",
    "score": "decay_linear(time, 24h) * (likes + comments + shares)"
]

Interest-based Ranking

// Rank based on user interests and engagement history
let interestRanking = [
    "type": "interest",
    "score": "decay_linear(time) * interest_match * engagement_prediction"
]

Aggregation & Notification Feeds

Aggregation groups similar activities together, which is useful for notification feeds and reducing noise.

Aggregation Types

// Count aggregation - shows "John and 5 others liked your post"
let countAggregation: [String: Any] = [
    "type": "count",
    "group_by": ["activity_id", "reaction_type"]
]
// Time-based aggregation - groups activities within a time window
let timeAggregation = [
    "type": "time",
    "window": "1h"
]
// User-based aggregation - groups activities by user
let userAggregation: [String: Any] = [
    "type": "user",
    "group_by": ["user_id"]
]

Notification Feed Example

// Create a notification feed with aggregation
let notificationFeed = client.feed(group: "notification", id: "john")

// Configure it with aggregation
let notificationConfig = [
    "aggregation": countAggregation,
    "ranking": ["type": "time", "direction": "desc"]
]

// Read notifications
let notifications = try await notificationFeed.getOrCreate()

Marking Notifications as Read

// Mark specific notifications as read
try await notificationFeed.markActivity(
    request: .init(
        markRead: ["notification_1", "notification_2"]
    )
)
// Mark all notifications as read
try await notificationFeed.markActivity(
    request: .init(
        markAllRead: true
    )
)
© Getstream.io, Inc. All Rights Reserved.