---
source_url: https://www.pubnub.com/docs/general/resources/design-pattern-friend-list-status-feeds
title: Friend List and Status Feed
updated_at: 2026-06-18T11:26:25.426Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Friend List and Status Feed

Using [Subscription Management](https://www.pubnub.com/docs/general/channels/subscribe) and the [Presence](https://www.pubnub.com/docs/general/presence/overview) service, you can create simple friend graphs and a real‑time status feed with PubNub. The friend graph here is a follow/follower model. For multi‑degree graphs or advanced traversal, use a graph database such as Neo4J. These complex cases are less common.

Channel Groups are essential. A Channel Group is a persistent collection of channels you modify dynamically. You subscribe to the group to receive messages from all member channels. Learn more about [Channel Groups](https://www.pubnub.com/docs/general/channels/subscribe#channel-groups).

Channel Group multiplexing enables friend lists and status feeds. The diagram and steps follow.

![Overview: subscribe for status messages and presence events via Channel Groups](https://www.pubnub.com/assets/images/summary-e54c0d6a294bc5aa9054fc560da240a0.png)

## User identification

Presence uses the User ID set during PubNub client initialization to track each user. See [Managing User IDs](https://www.pubnub.com/docs/general/setup/users-and-devices).

## User channels

Every user will need two channels:

* A channel the user publishes status messages to.
* A channel the user subscribes to for online status. This channel is unique per user. For examples, we use user‑[letter], such as `user-a` or `user-b`.

![Per‑user channels: status publish and present subscribe](https://www.pubnub.com/assets/images/user-channels-f49e326f476404eac4b8ed39078695e0.png)

Add these channels to Channel Groups to aggregate status messages into a feed and to monitor presence for online status. You can implement either feature independently.

![Two Channel Groups per user: friends presence and status feed](https://www.pubnub.com/assets/images/user-channel-groups-d0dbd9f613492445fcaf5c9c866cfc5b.png)

:::note Channel Group management
Call the Add/Remove Channel to/from Channel Group APIs from your server (for example, on account registration). Doing this on the client reduces channel security.
:::

###### JavaScript

```javascript
// Publish a status message
pubnub.publish(
  {
    channel: "ch-user-a-status",
    message: {
      author: "user-a",
      status: "I am reading about Advanced Channel Groups!",
      timestamp: Date.now() / 1000
    }
  },
  function (status, response) {
    if (status.error) {
      console.log(status);
    }
    else {
      console.log("message Published w/ timetoken", response.timetoken);
    }
  }
);
```

###### Swift

```swift
let timestamp = Date().timeIntervalSince1970

pubnub.publish(
  channel: "channelSwift",
  message: [
    "author": "user-a",
    "status": "I am reading about Advanced Channel Groups!",
    "timestamp": timestamp
  ]
) { result in
  switch result {
    case let .success(response):
      print("Successful Publish Response: \(response)")
    case let .failure(error):
      print("Failed Publish Response: \(error.localizedDescription)")
  }
}
```

###### Objective-C

```objectivec
NSTimeInterval timestamp = [NSDate date].timeIntervalSince1970;
NSDictionary *data = @{
  @"author": @"user-a",
  @"status": @"I am reading about Advanced Channel Groups!",
  @"timestamp": @(timestamp)
};

[self.pubnub publish:data toChannel:@"ch-user-a-status"
  withCompletion:^(PNPublishStatus *status) {
    if (!status.isError) {
      NSLog(@"Message Published w/ timetoken: %@", status.data.timetoken);
    }
    else {
      NSLog(@"Error happened while publishing: %@", status.errorData.information);
    }
  }
];
```

###### Java

```java
Date date = new Date();
JsonObject data = new JsonObject();
data.put("author", "user-a");
data.put("status", "I am reading about Advanced Channel Groups!");
data.put("timestamp", date.getTime() / 1000);

pubnub.publish()
  .channel("ch-user-a-status")
  .message(data)
  .async(result -> {
      result.onSuccess(res -> {
          System.out.println("message Published w/ timetoken "
                  + res.getTimetoken());
      }).onFailure(exception -> {
          System.out.println("error happened while publishing: "
                  + exception.toString());
      });
  });
```

###### C#

```csharp
// Publish a status message
pubnub.Publish()
  .Message(data)
  .Channel("ch-user-a-status")
  .Execute(new DemoPublishResult());

public class DemoPublishResult : PNCallback<PNPublishResult> {
  public override void OnResponse(PNPublishResult result, PNStatus status) {
    if (status.Error) {
      Console.WriteLine("error happened while publishing: " +
        pubnub.JsonPluggableLibrary.SerializeToJsonString(status));
    }
    else {
      Console.WriteLine("message Published w/ timetoken "
        + result.Timetoken.ToString());
    }
  }
};
```

###### Python

```python
# Publish a status message
import time

data = {
  'author': 'user-a',
  'status': 'I am reading about Advanced Channel Groups!',
  'timestamp': time.time()
}

try:
  envelope = pubnub.publish()\
    .channel("ch-user-b-present")\
    .message(data)\
    .sync()

  print("Message published with timetoken %s" % envelope.result.timetoken)
except PubNubException as e:
  print("Operation failed w/ status: %s" % e)
```

## User channel groups

Each user will also have two channel groups:

* A channel group for observing the online status of friends.
* A channel group to receive status updates in real time.

Again, we'll use the same convention for unique user identifiers:

* `cg-user-a-friends`
* `cg-user-a-status-feed`

![Two Channel Groups](https://www.pubnub.com/assets/images/user-channel-groups-d0dbd9f613492445fcaf5c9c866cfc5b.png)

Creating channel groups requires you to add at least one channel to the channel group. The easiest way to do this is add the user's *present* channel (`ch-user-a-present`) to each channel group.

:::note Channel Group Management
You should only call the *Add/Remove Channel to/from Channel Group* APIs from your back-end server when a user registers for an account. Doing so from the client side reduces the security of your channels.
:::

###### JavaScript

```javascript
// Add ch-user-a-present to cg-user-a-friends
pubnub.channelGroups.addChannels(
  {
    channels: ["ch-user-a-present"],
    channelGroup: "cg-user-a-friends",
  },
  function(status) {
    if (status.error) {
      console.log("operation failed w/ status: ", status);
    }
    else {
      console.log("Channel added to channel group");
    }
  }
);

// Add ch-user-a-present to cg-user-a-status-feed
pubnub.channelGroups.addChannels(
  {
    channels: ["ch-user-a-present"],
    channelGroup: "cg-user-a-status-feed",
  },
  function(status) {
    if (status.error) {
      console.log("operation failed w/ status: ", status);
    }
    else {
      console.log("Channel added to channel group");
    }
  }
);
```

###### Swift

```swift
// Add ch-user-a-present to cg-user-a-friends
pubnub.addChannels(
  ["ch-user-a-present"],
  to: "cg-user-a-friends"
) { result in
  switch result {
    case let .success(response):
      print("Successful Add Channels Response: \(response)")
    case let .failure(error):
      print("Failed Add Channels Response: \(error.localizedDescription)")
  }
}

// Add ch-user-a-present to cg-user-a-status-feed
pubnub.addChannels(
  ["ch-user-a-present"],
  to: "cg-user-a-feed"
) { result in
  switch result {
    case let .success(response):
      print("Successful Add Channels Response: \(response)")
    case let .failure(error):
      print("Failed Add Channels Response: \(error.localizedDescription)")
  }
}
```

###### Objective-C

```objectivec
// Add ch-user-a-present to cg-user-a-friends
[self.client addChannels:@[@"ch-user-a-present"] toGroup:@"cg-user-a-friends"
  withCompletion:^(PNAcknowledgmentStatus *status) {

    if (!status.isError) {
      NSLog(@"Channel added to channel group.");
    }
    else {
      NSLog(@"Operation failed w/ status: %@", status.errorData.information);
    }
  }
];

// Add ch-user-a-present to cg-user-a-status-feed
[self.client addChannels:@[@"ch-user-a-present"] toGroup:@"cg-user-a-status-feed"
  withCompletion:^(PNAcknowledgmentStatus *status) {

    if (!status.isError) {
      NSLog(@"Channel added to channel group.");
    }
    else {
      NSLog(@"Operation failed w/ status: %@", status.errorData.information);
    }
  }
];
```

###### Java

```java
// Add ch-user-a-present to cg-user-a-friends
pubnub.addChannelsToChannelGroup()
  .channelGroup("cg-user-a-friends")
  .channels(Arrays.asList("ch-user-a-present"))
  .async(result -> {
      result.onSuccess(res -> {
          System.out.println("Channel added to channel group");
      }).onFailure(exception -> {
          System.out.println("Operation failed w/ status: "
                + exception.toString());
      });
  });

// Add ch-user-a-present to cg-user-a-status-feed
pubnub.addChannelsToChannelGroup()
  .channelGroup("cg-user-a-status-feed")
  .channels(Arrays.asList("ch-user-a-present"))
  .async(result -> {
        result.onSuccess(res -> {
            System.out.println("Channel added to channel group");
        }).onFailure(exception -> {
            System.out.println("Operation failed w/ status: "
                  + exception.toString());
        });
    });
```

###### C#

```csharp
// Add ch-user-a-present to cg-user-a-friends
pubnub.AddChannelsToChannelGroup()
  .ChannelGroup("cg-user-a-friends")
  .Channels(new string[] { "ch-user-a-present" })
  .Execute(new DemoChannelGroupAddChannel());

// Add ch-user-a-present to cg-user-a-status-feed
pubnub.AddChannelsToChannelGroup()
  .ChannelGroup("cg-user-a-status-feed")
  .Channels(new string[] { "ch-user-a-present" })
  .Execute(new DemoChannelGroupAddChannel());

public class DemoChannelGroupAddChannel : PNCallback<PNChannelGroupsAddChannelResult> {
  public override void OnResponse(PNChannelGroupsAddChannelResult result, PNStatus status) {
    if (status.Error) {
      Console.WriteLine("Operation failed w/ status: " + status.StatusCode.ToString());
    }
    else {
      Console.WriteLine("Channel added to channel group");
    }
  }
}
```

###### Python

```python
# Add ch-user-a-present to cg-user-a-friends
pubnub.add_channel_to_channel_group()\
  .channels(["ch-user-a-present"])\
  .channel_group("cg-user-a-friends")\
  .sync()

# Add ch-user-a-present to cg-user-a-status-feed
pubnub.add_channel_to_channel_group()\
  .channels(["ch-user-a-present"])\
  .channel_group("cg-user-a-status-feed")\
  .sync()
```

## Friending

Expanding the friend graph through friending is straightforward: you add channels to channel groups. When User A and User B become friends, you add each user's `-present` channel to the other's friend group, and add the `-status` channel to each user's `status-feed` group. Again, you should only call these APIs from your back-end server when you receive the friendship confirmation from both users.

![Users Friends Group](https://www.pubnub.com/assets/images/friending-93d6905b9fa15428d0105b1ff9d1f25e.png)

**User A and User B become friends**:

### JavaScript

```javascript
// ************************************
// * User A and User B become friends
// ************************************

// Add User B to User A's groups: Add ch-user-b-present to cg-user-a-friends
pubnub.channelGroups.addChannels(
  {
    channels: ["ch-user-b-present"],
    channelGroup: "cg-user-a-friends"
  },
  function(status) {
    if (status.error) {
      console.log("operation failed w/ status: ", status);
    }
    else {
      console.log("Channel added to channel group");
    }
  }
);

// Add User B to User A's groups: ch-user-b-status to cg-user-a-status-feed
pubnub.channelGroups.addChannels(
  {
    channels: ["ch-user-b-status"],
    channelGroup: "cg-user-a-status-feed"
  },
  function(status) {
    if (status.error) {
      console.log("operation failed w/ status: ", status);
    }
    else {
      console.log("Channel added to channel group");
    }
  }
);

// Add User A to User B's groups: Add ch-user-a-present to cg-user-b-friends
pubnub.channelGroups.addChannels(
  {
    channels: ["ch-user-a-present"],
    channelGroup: "cg-user-b-friends"
  },
  function(status) {
    if (status.error) {
      console.log("operation failed w/ status: ", status);
    }
    else {
      console.log("Channel added to channel group");
    }
  }
);

// Add User B to User A's groups: ch-user-a-status to cg-user-b-status-feed
pubnub.channelGroups.addChannels(
  {
    channels: ["ch-user-a-status"],
    channelGroup: "cg-user-b-status-feed"
  },
  function(status) {
    if (status.error) {
      console.log("operation failed w/ status: ", status);
    }
    else {
      console.log("Channel added to channel group");
    }
  }
);
```

### Swift

```swift
// ************************************
// * User A and User B become friends
// ************************************

// Add User B to User A's groups: Add ch-user-b-present to cg-user-a-friends
pubnub.addChannels(
  ["ch-user-a-present"],
  to: "cg-user-a-friends"
) { result in
  switch result {
    case let .success(response):
      print("Successful Add Channels Response: \(response)")
    case let .failure(error):
      print("Failed Add Channels Response: \(error.localizedDescription)")
  }
}

// Add User B to User A's groups: ch-user-b-status to cg-user-a-status-feed
pubnub.addChannels(
  ["ch-user-a-status"],
  to: "cg-user-a-feed"
) { result in
  switch result {
    case let .success(response):
      print("Successful Add Channels Response: \(response)")
    case let .failure(error):
      print("Failed Add Channels Response: \(error.localizedDescription)")
  }
}

// Add User A to User B's groups: Add ch-user-a-present to cg-user-b-friends
pubnub.addChannels(
  ["ch-user-a-present"],
  to: "cg-user-b-friends"
) { result in
  switch result {
    case let .success(response):
      print("Successful Add Channels Response: \(response)")
    case let .failure(error):
      print("Failed Add Channels Response: \(error.localizedDescription)")
  }
}

// Add User B to User A's groups: ch-user-a-status to cg-user-b-status-feed
pubnub.addChannels(
  ["ch-user-a-status"],
  to: "cg-user-b-feed"
) { result in
  switch result {
    case let .success(response):
      print("Successful Add Channels Response: \(response)")
    case let .failure(error):
      print("Failed Add Channels Response: \(error.localizedDescription)")
  }
}
```

### Objective-C

```objectivec
// Add User B to User A's groups: Add ch-user-b-present to cg-user-a-friends
[self.pubnub addChannels:@[@"ch-user-b-present"] toGroup:@"cg-user-a-friends"
  withCompletion:^(PNAcknowledgmentStatus *status) {

    if (!status.isError) {
      NSLog(@"Channel added to channel group.");
    }
    else {
      NSLog(@"Operation failed w/ status: %@", status.errorData.information);
    }
  }
];

// Add User B to User A's groups: ch-user-b-status to cg-user-a-status-feed
[self.pubnub addChannels:@[@"ch-user-b-status"] toGroup:@"cg-user-a-status-feed"
  withCompletion:^(PNAcknowledgmentStatus *status) {

    if (!status.isError) {
      NSLog(@"Channel added to channel group.");
    }
    else {
      NSLog(@"Operation failed w/ status: %@", status.errorData.information);
    }
  }
];

// Add User A to User B's groups: Add ch-user-a-present to cg-user-b-friends
[self.pubnub addChannels:@[@"ch-user-a-present"] toGroup:@"cg-user-b-friends"
  withCompletion:^(PNAcknowledgmentStatus *status) {

  if (!status.isError) {
    NSLog(@"Channel added to channel group.");
  }
  else {
    NSLog(@"Operation failed w/ status: %@", status.errorData.information);
  }
}];

// Add User B to User A's groups: ch-user-a-status to cg-user-b-status-feed
[self.pubnub addChannels:@[@"ch-user-a-status"] toGroup:@"cg-user-b-status-feed"
  withCompletion:^(PNAcknowledgmentStatus *status) {

    if (!status.isError) {
      NSLog(@"Channel added to channel group.");
    }
    else {
      NSLog(@"Operation failed w/ status: %@", status.errorData.information);
    }
  }
];
```

### Java

```java
// ************************************
// * User A and User B become friends
// ************************************

// Add User B to User A's groups: Add ch-user-b-present to cg-user-a-friends
pubnub.addChannelsToChannelGroup()
    .channelGroup("cg-user-a-friends")
    .channels(Arrays.asList("ch-user-b-present"))
    .async(result -> {
        result.onSuccess(res -> {
            System.out.println("Channel added to channel group");
        }).onFailure(exception -> {
            System.out.println("Operation failed w/ status:" + exception.toString());
        });
    });

// Add User B to User A's groups: ch-user-b-status to cg-user-a-status-feed
pubnub.addChannelsToChannelGroup()
  .channelGroup("cg-user-a-status-feed")
  .channels(Arrays.asList("ch-user-b-status"))
    .async(result -> {
        result.onSuccess(res -> {
            System.out.println("Channel added to channel group");
        }).onFailure(exception -> {
            System.out.println("Operation failed w/ status:" + exception.toString());
        });
    });

// Add User A to User B's groups: Add ch-user-a-present to cg-user-b-friends
pubnub.addChannelsToChannelGroup()
  .channelGroup("cg-user-b-friends")
  .channels(Arrays.asList("ch-user-a-present"))
  .async(result -> {
      result.onSuccess(res -> {
          System.out.println("Channel added to channel group");
      }).onFailure(exception -> {
          System.out.println("Operation failed w/ status:" + exception.toString());
      });
  });

// Add User B to User A's groups: ch-user-a-status to cg-user-b-status-feed
pubnub.addChannelsToChannelGroup()
    .channelGroup("cg-user-b-status-feed")
    .channels(Arrays.asList("ch-user-a-status"))
    .async(result -> {
        result.onSuccess(res -> {
            System.out.println("Channel added to channel group");
        }).onFailure(exception -> {
            System.out.println("Operation failed w/ status:" + exception.toString());
        });
    });
```

### C#

```csharp
// ************************************
// * User A and User B become friends
// ************************************

// Add ch-user-a-present to cg-user-a-friends
pubnub.AddChannelsToChannelGroup()
  .ChannelGroup("cg-user-a-friends")
  .Channels(new string[] { "ch-user-b-present" })
  .Execute(new DemoChannelGroupAddChannel());

// Add User B to User A's groups: ch-user-b-status to cg-user-a-status-feed
pubnub.AddChannelsToChannelGroup()
  .ChannelGroup("cg-user-a-status-feed")
  .Channels(new string[] { "ch-user-b-status" })
  .Execute(new DemoChannelGroupAddChannel());

// Add User A to User B's groups: Add ch-user-a-present to cg-user-b-friends
pubnub.AddChannelsToChannelGroup()
  .ChannelGroup("cg-user-b-friends")
  .Channels(new string[] { "ch-user-a-present" })
  .Execute(new DemoChannelGroupAddChannel());

// Add User B to User A's groups: ch-user-a-status to cg-user-b-status-feed
pubnub.AddChannelsToChannelGroup()
  .ChannelGroup("cg-user-b-status-feed")
  .Channels(new string[] { "ch-user-a-status" })
  .Execute(new DemoChannelGroupAddChannel());

public class DemoChannelGroupAddChannel : PNCallback<PNChannelGroupsAddChannelResult> {
  public override void OnResponse(PNChannelGroupsAddChannelResult result, PNStatus status) {
    if (status.Error) {
      Console.WriteLine("Operation failed w/ status: "
        + status.StatusCode.ToString());
    }
    else {
      Console.WriteLine("Channel added to channel group");
    }
  }
}
```

### Python

```python
# ************************************
# * User A and User B become friends
# ************************************

try:
  result = pubnub.add_channel_to_channel_group()\
    .channel_group("cg-user-a-friends")\
    .channels("ch-user-b-present")\
    .sync()

  print("Channel added to channel group")
except PubNubException as e:
  print("Operation failed w/ status: %s" % e)

try:
  result = pubnub.add_channel_to_channel_group()\
    .channel_group("cg-user-a-status-feed")\
    .channels("ch-user-b-status")\
    .sync()

  print("Channel added to channel group")
except PubNubException as e:
  print("Operation failed w/ status: %s" % e)

try:
  result = pubnub.add_channel_to_channel_group()\
    .channel_group("cg-user-b-friends")\
    .channels("ch-user-a-present")\
    .sync()

  print("Channel added to channel group")
except PubNubException as e:
  print("Operation failed w/ status: %s" % e)

try:
  result = pubnub.add_channel_to_channel_group()\
    .channel_group("cg-user-b-status-feed")\
    .channels("ch-user-a-status")\
    .sync()

  print("Channel added to channel group")
except PubNubException as e:
  print("Operation failed w/ status: %s" % e)
```

### Subscribe

To see these working, it comes to how you subscribe. What is a bit different here is that you'll subscribe to one channel group for messages (status-feed) but subscribe to the other channel group's *presence event* channel group for the online/offline status of friends.

## Friends Online/Offline

:::note User ID / UUID
User ID is also referred to as **UUID/uuid** in some APIs and server responses but **holds the value** of the **userId** parameter you [set during initialization](https://www.pubnub.com/docs/general/setup/users-and-devices#set-the-user-id).
:::

For presence, we track the subscribers on a channel, and we create a *side* channel based on the channel name for all the presence events on the main channel. This *side* channel is the channel name + `-pnpres`. So for channel `ch-user-a-present`, the presence *side* channel is `ch-user-a-present-pnpres` and that is where PubNub publishes presence events that occur on `ch-user-a-present`.

The presence events are as follows:

* `join` - client subscribed
* `leave` - client unsubscribed
* `timeout` - client disconnected without unsubscribing (occurs after the timeout period expires)
* `state-change` - client changed the contents of the state object

And for each event, we also include the User ID and the channel occupancy (how many subscribers).

To see your friends online/offline status and be updated in real time, you subscribe to the friends channel group, but not directly. You subscribe to the presence event *side* channel group only, by appending `-pnpres` to the channel group name.

The reason you do not want to subscribe directly to the channel group is because Channel Groups are `Subscribe` groups, and therefore you would inadvertently subscribe to all of that users' friends' `presence` channels. We are only interested in the *presence events* of this channel group and not the message of those users.

![Friends Presence](https://www.pubnub.com/assets/images/friends-presence-1948cb7da6c86c6780829b7124d046f1.png)

###### JavaScript

```javascript
// Get the List of Friends
pubnub.channelGroups.listChannels(
  {
    channelGroup: "cg-user-a-friends"
  },
  function (status, response) {
  if (status.error) {
    console.log("operation failed w/ error:", status);
    return;
  }

    console.log("FRIENDLIST: ")
    response.channels.forEach( function (channel) {
      console.log(channel);
    });
  }
);

// Which Friends are online right now
pubnub.hereNow(
  {
    channelGroups: ["cg-user-a-friends"]
  },
  function (status, response) {
    if (status.error) {
      console.log("operation failed w/ error:", status);
    }
    else {
      console.log("ONLINE NOW: ", response);
    }
  }
);

pubnub.addListener({
  presence: function(presence) {
    console.log("FRIEND PRESENCE: ", presence);
  },
});

// Watch Friends come online / go offline
pubnub.subscribe({channelGroups:["cg-user-a-friends-pnpres"]});
```

###### Swift

```swift
// Get the List of Friends
pubnub.listChannels(for: "cg-user-a-friends") { result in
  switch result {
    case let .success(response):
      print("Successful List Channels Response: \(response)")
    case let .failure(error):
      print("Failed List Channels Response: \(error.localizedDescription)")
  }
}

// Which Friends are online right now
pubnub.hereNow(
  on: [],
  and: ["cg-user-a-friends"]
) { result in
  switch result {
    case let .success(response):
      print("Successful hereNow Response: \(response)")
    case let .failure(error):
      print("Failed hereNow Response: \(error.localizedDescription)")
  }
}

// Watch Friends come online / go offline
pubnub.subscribe(
  to: [],
  and: ["cg-user-a-friends"],
  withPresence: true
) { result in
  switch result {
    case let .success(response):
      print("Successful Response: \(response)")
    case let .failure(error):
      print("Failed Response: \(error.localizedDescription)")
  }
}
```

###### Objective-C

```objectivec
// Get the List of Friends
[self.pubnub channelsForGroup:@"cg-user-a-friends"
  withCompletion:^(PNChannelGroupChannelsResult *result, PNErrorStatus *status) {

    if (!status) {
      NSLog(@"Friendslist: %@", result.data.channels);
    }
    else {
      NSLog(@"Operation failed w/ status: %@", status.errorData.information);
    }
  }
];

// Which Friends are online right now
[self.client hereNowForChannelGroup:@"cg-user-a-friends"
  withCompletion:^(PNPresenceChannelGroupHereNowResult *result, PNErrorStatus *status) {

    if (!status) {
      NSLog(@"Online now: %@", result.data.totalOccupancy);
    }
    else {
      NSLog(@"Operation failed w/ status: %@", status.errorData.information);
    }
  }
];

// Watch Friends come online / go offline
[self.pubnub addListener:self];
[self.pubnub subscribeToChannelGroups:@[@"cg-user-a-friends-pnpres"]];

- (void)pubnub:(PubNub *)client didReceivePresenceEvent:(PNPresenceEventResult *)event {
  if (![event.data.presenceEvent isEqualToString:@"state-change"]) {
    NSLog(@"%@ \"%@'ed\"", event.data.presence.uuid, event.data.presenceEvent);
  }
}
```

###### Java

```java
// Get the List of Friends
pubnub.listChannelsForChannelGroup()
  .channelGroup("cg-user-a-friends")
  .async(result -> {
      result.onSuccess(res -> {
          System.out.println("FRIENDLIST:");
          res.getChannels().stream().forEach((channelName) -> {
              System.out.println("channels: " + channelName);
          });
      }).onFailure(exception -> {
          System.out.println("operation failed w/ status:" + exception.toString());
      });
  });

// Which Friends are online right now
pubnub.hereNow()
  .channels(Arrays.asList("cg-user-a-friends"))
  .async(result -> {
      result.onSuccess(res -> {
          System.out.println("ONLINE NOW: " + res.getTotalOccupancy());
      }).onFailure(exception -> {
          System.out.println("operation failed w/ status:" + exception.toString());
      });
  });

pubnub.addListener(new EventListener() {
  @Override
  public void presence(PubNub pubnub, PNPresenceEventResult presence) {
    System.out.println("FRIEND PRESENCE: " + presence);
  }
});

// Watch Friends come online / go offline
pubnub.subscribe()
  .channelGroups(Arrays.asList("cg-user-a-friends-pnpres"))
  .execute();
```

###### C#

```csharp
// Get the List of Friends
pubnub.ListChannelsForChannelGroup()
  .ChannelGroup("cg-user-a-friends")
  .Execute(new DemoListChannelGroupAllChannels());

// Which Friends are online right now
pubnub.HereNow()
  .ChannelGroups(new string[] { "cg-user-a-friends" })
  .Execute(new DemoHereNowResult());

// Watch Friends come online / go offline
ChannelGroup friendsGroupPresence = pubnub.ChannelGroup("cg-user-a-friends-pnpres");
Subscription friendsGroupPresenceSubscription = friendsGroupPresence.Subscription();

friendsGroupPresenceSubscription.AddListener(new DemoSubscribeCallback());
friendsGroupPresenceSubscription.Subscribe<string>();

public class DemoListChannelGroupAllChannels : PNCallback<PNChannelGroupsAllChannelsResult> {
  public override void OnResponse(PNChannelGroupsAllChannelsResult result, PNStatus status) {
    if (status.Error) {
      Console.WriteLine("Operation failed w/ status: " + status.StatusCode.ToString());
    }
    else {
      Console.WriteLine("FRIENDLIST:");

      foreach(string channelName in result.Channels) {
        Console.WriteLine("channel:" + channelName);
      }
    }
  }
}

public class DemoHereNowResult : PNCallback<PNHereNowResult> {
  public override void OnResponse(PNHereNowResult result, PNStatus status) {
    if (status.Error) {
      Console.WriteLine("Operation failed w/ status: " + status.StatusCode.ToString());
    }
    else {
      Console.WriteLine("ONLINE NOW:" + result.TotalOccupancy.ToString());
    }
  }
};

public class DemoSubscribeCallback : SubscribeCallback {
  public override void Message<T>(Pubnub pubnub, PNMessageResult<T> message) {
  }

  public override void Presence(Pubnub pubnub, PNPresenceEventResult presence) {
    Console.WriteLine("FRIEND PRESENCE:" + pubnub.JsonPluggableLibrary.SerializeToJsonString(presence));
  }

  public override void Status(Pubnub pubnub, PNStatus status) {
  }
}
```

###### Python

```python
# Get the List of Friends

try:
  env = pubnub.list_channels_in_channel_group()\
    .channel_group("cg-user-a-friends")\
    .sync()

  print("FRIEND LIST:")

  for channel in env.result.channels:
    print(channel)
except PubNubException as e:
  print("Operation failed w/ status: %s" % e)

try:
  env = pubnub.here_now()\
    .channels("cg-user-a-status") \
    .sync()

  print("ONLINE NOW: %d" % env.result.total_occupancy)

except PubNubException as e:
  print("Operation failed w/ status: %s" % e)

class PresenceListener(SubscribeCallback):
  def status(self, pubnub, status):
    pass

  def message(self, pubnub, message):
    pass

  def presence(self, pubnub, presence):
    print("FRIEND PRESENCE: %s" % presence)

my_listener = PresenceListener()

pubnub.add_listener(my_listener)

pubnub.subscribe()\
  .channel_groups("cg-user-a-friends")\
  .with_presence()\
  .execute()
```

## Status feed (messages)

The status-feed channel group is much more straightforward, you're going to subscribe directly to the channel group and you'll receive status updates in real time via each channel in the channel group.

![Status Feed](https://www.pubnub.com/assets/images/status-feed-ff28486b8acd13422e365750dbd9ab7c.png)

Since we include the user's present channel (`ch-user-a-present`) for this user in the channel group, this will also have the net effect of subscribing to that channel. It also means it generates a `join` event for every channel group that includes this user's present channel. So, if User B is friends with User A, when User A subscribes to this status-feed channel group, it also subscribes to User A's present channel and generates that `join` event, in addition to all the other presence events.

:::tip Friend List Only Implementation
If you're implementing only the friend list and not the status feed, User A will need to subscribe to this `ch-user-a-present` channel directly since User A isn't subscribing to the status feed group which includes this channel.
:::

###### JavaScript

```javascript
pubnub.addListener({
  message: function(message) {
    console.log("STATUS: ", message);
  }
});

// Get Status Feed Messages
pubnub.subscribe({channelGroups: ["cg-user-a-status-feed"]});
```

###### Swift

```swift
// Get Status Feed Messages
pubnub.subscribe(
  to: [],
  and: ["cg-user-a-friends"],
  withPresence: true
) { result in
  switch result {
    case let .success(response):
      print("Successful Response: \(response)")
    case let .failure(error):
      print("Failed Response: \(error.localizedDescription)")
  }
}
```

###### Objective-C

```objectivec
// Get Status Feed Messages
[self.pubnub addListener:self];
[self.pubnub subscribeToChannelGroups:@[@"cg-user-a-friends"] withPresence:YES];

- (void)client:(PubNub *)client didReceiveMessage:(PNMessageResult *)message {
  NSLog(@"Status: %@", message.data.message);
}
```

###### Java

```java
pubnub.addListener(new EventListener() {
  @Override
  public void message(PubNub pubnub, PNMessageResult message) {
    System.out.println("STATUS" + message);
  }
});

// Get Status Feed Messages
pubnub.subscribe()
  .channelGroups(Arrays.asList("cg-user-a-friends"))
  .execute();
```

###### C#

```csharp
pubnub.AddListener(new DemoSubscribeCallback());

// Get Status Feed Messages
var friendsGroup = pubnub.ChannelGroup("cg-user-a-friends");
Subscription friendsGroupSubscription = friendsGroup.Subscription();

friendsGroupSubscription.Subscribe<string>();

public class DemoSubscribeCallback : SubscribeCallback {
  public override void Message<T>(Pubnub pubnub, PNMessageResult<T> message) {
    Console.WriteLine("STATUS:" +
      pubnub.JsonPluggableLibrary.SerializeToJsonString(message));
  }

  public override void Presence(Pubnub pubnub, PNPresenceEventResult presence) {
  }

  public override void Status(Pubnub pubnub, PNStatus status) {
  }
}
```

###### Python

```python
channel_group = pubnub.channel_group("cg-user-a-friends")
cg_subscription = channel_group.subscription()

# Add event-specific listeners

# using closure for reusable listener
def on_message(listener):
    def message_callback(message):
        print(f"\033[94mMessage received on: {listener}: \n{message.message}\033[0m\n")
    return message_callback

# without closure
def on_presence(listener):
    def presence_callback(presence):
        print(f"\033[0;32mPresence received on: {listener}: \t{presence.uuid} {presence.event}s "
              f"{presence.subscription or presence.channel}\033[0m")
    return presence_callback

cg_subscription.on_message = on_message('message_listener')
cg_subscription.on_presence = on_presence

# add generic listener
class PrintListener(SubscribeCallback):
    def status(self, _, status):
        print(f'\033[92mPrintListener.status:\n{status.category.name}\033[0m')

cg_subscription.add_listener(PrintListener())

cg_subscription = channel_group.subscription().subscribe()
```

## Retrieve history

Retrieving history of status messages can be a bit more work as you have to retrieve messages from each channel in the group individually (each friend) and mash/sort them together client side. You can retrieve message from multiple channels in a single request, but you still have to mash/sort them together when you get those messages.

## Complex use cases

There are other complex use cases of changing status feeds that can make things a bit trickier, one is *weighting* the status message, so that it's no longer chronological, but rather based on interests, or activity, or other things. Another layer of complexity that you can add is commenting on status items. Again, it requires a bit more logic here, and PubNub has some additional features, like [Message Actions](https://www.pubnub.com/docs/general/messages/actions), that will provide some assistance.

## Summary

Simple and powerful friend graphs are fairly easy to do with PubNub. It's much easier than trying to develop a full back end to support the presence and status feeds, and on top of that, it's real-time.

## Terms in this document

* **PubNub** - PubNub is a real-time messaging platform that provides APIs and SDKs for building scalable applications. It handles the complex infrastructure of real-time communication, including: Message delivery and persistence, Presence detection, Access control, Push notifications, File sharing, Serverless processing with Functions and Events & Actions, Analytics and monitoring with BizOps Workspace, AI-powered insights with Illuminate.
