> ## Documentation Index
> Fetch the complete documentation index at: https://docs.contactsmanager.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Social Features

> Implementing social features with the ContactsManager SDK

# Social Features

The ContactsManager SDK provides comprehensive social features to enhance your app with follow relationships and activity feeds. These features enable your users to connect with each other, follow other users of interest, and create an engaging social experience within your application. This guide explains how to implement these features using the SDK.

## Follow Relationships

Follow relationships are the foundation of social connectivity in your app. They allow users to curate their network by following specific users, which determines whose content appears in their feeds and activity streams.

### Following a User

Allow users to follow other users in your app. Following creates a one-way relationship where a user can see updates from people they follow without requiring mutual connection.

<CodeGroup>
  ```swift Swift theme={null}
  do {
      // Follow a user using their organization-specific user ID
      let result = try await ContactsService.shared.socialService.followUser(
          userId: "user-id"
      )
      
      if let success = result.success, success {
          print("Successfully followed user")
      } else if let alreadyFollowing = result.alreadyFollowing, alreadyFollowing {
          print("Already following this user")
      }
  } catch {
      print("Error following user: \(error.localizedDescription)")
  }
  ```

  ```jsx React Native theme={null}
  import { followUser } from '@contactsmanager/rn';

  try {
    // Follow a user using their organization-specific user ID
    const result = await followUser('user-id');
    
    if (result.success) {
      console.log('Successfully followed user');
    } else if (result.alreadyFollowing) {
      console.log('Already following this user');
    }
  } catch (error) {
    console.error('Error following user:', error);
  }
  ```

  ```kotlin Kotlin theme={null}
  // Follow a user using their organization-specific user ID
  try {
      val result = SocialService.getInstance(context)
          .followUser("user-id")
          .getOrThrow()
      
      if (result.success) {
          println("Successfully followed user")
      } else if (result.alreadyFollowing) {
          println("Already following this user")
      }
  } catch (e: Exception) {
      println("Error following user: ${e.message}")
  }
  ```

  ```objectivec Objective-C theme={null}
  // Follow a user using their organization-specific user ID
  [[CMContactService sharedInstance].socialService followUser:@"user-id" 
      completion:^(CMFollowActionResponse * _Nullable response, NSError * _Nullable error) {
          if (error) {
              NSLog(@"Error following user: %@", error.localizedDescription);
              return;
          }
          
          if (response.success) {
              NSLog(@"Successfully followed user");
          } else if (response.alreadyFollowing) {
              NSLog(@"Already following this user");
          }
      }];
  ```
</CodeGroup>

The `followUser` method requires:

* `userId`: The organization-specific user ID of the user to follow

The response indicates whether the follow action was successful or if the user was already following the specified user.

### Unfollowing a User

Users may want to stop following certain users. The unfollow action removes the relationship, preventing that user's updates from appearing in the user's feed.

<CodeGroup>
  ```swift Swift theme={null}
  do {
      let result = try await ContactsService.shared.socialService.unfollowUser(
          userId: "user-id"
      )
      
      if let success = result.success, success {
          print("Successfully unfollowed user")
      }
  } catch {
      print("Error unfollowing user: \(error.localizedDescription)")
  }
  ```

  ```jsx React Native theme={null}
  import { unfollowUser } from '@contactsmanager/rn';

  try {
    const result = await unfollowUser('user-id');
    
    if (result.success) {
      console.log('Successfully unfollowed user');
    }
  } catch (error) {
    console.error('Error unfollowing user:', error);
  }
  ```

  ```kotlin Kotlin theme={null}
  try {
      val result = SocialService.getInstance(context)
          .unfollowUser("user-id")
          .getOrThrow()
      
      if (result.success) {
          println("Successfully unfollowed user")
      }
  } catch (e: Exception) {
      println("Error unfollowing user: ${e.message}")
  }
  ```

  ```objectivec Objective-C theme={null}
  [[CMContactService sharedInstance].socialService unfollowUser:@"user-id" 
      completion:^(CMFollowActionResponse * _Nullable response, NSError * _Nullable error) {
          if (error) {
              NSLog(@"Error unfollowing user: %@", error.localizedDescription);
              return;
          }
          
          if (response.success) {
              NSLog(@"Successfully unfollowed user");
          }
      }];
  ```
</CodeGroup>

The `unfollowUser` method requires only the organization-specific user ID of the user to unfollow.

### Checking Follow Status

Determining if a user is following another user is essential for displaying the correct UI state, such as follow/unfollow buttons on profile pages.

<CodeGroup>
  ```swift Swift theme={null}
  do {
      let status = try await ContactsService.shared.socialService.isFollowingUser(
          userId: "user-id"
      )
      
      if status.isFollowing {
          print("You are following this user")
      } else {
          print("You are not following this user")
      }
  } catch {
      print("Error checking follow status: \(error.localizedDescription)")
  }
  ```

  ```jsx React Native theme={null}
  import { isFollowingUser } from '@contactsmanager/rn';

  try {
    const status = await isFollowingUser('user-id');
    
    if (status.isFollowing) {
      console.log('You are following this user');
    } else {
      console.log('You are not following this user');
    }
  } catch (error) {
    console.error('Error checking follow status:', error);
  }
  ```

  ```kotlin Kotlin theme={null}
  try {
      val status = SocialService.getInstance(context)
          .isFollowingUser("user-id")
          .getOrThrow()
      
      if (status.isFollowing) {
          println("You are following this user")
      } else {
          println("You are not following this user")
      }
  } catch (e: Exception) {
      println("Error checking follow status: ${e.message}")
  }
  ```

  ```objectivec Objective-C theme={null}
  [[CMContactService sharedInstance].socialService isFollowingUser:@"user-id" 
      completion:^(CMFollowStatusResponse * _Nullable response, NSError * _Nullable error) {
          if (error) {
              NSLog(@"Error checking follow status: %@", error.localizedDescription);
              return;
          }
          
          if (response.isFollowing) {
              NSLog(@"You are following this user");
          } else {
              NSLog(@"You are not following this user");
          }
      }];
  ```
</CodeGroup>

The `isFollowingUser` method returns a status object with an `isFollowing` property that indicates the current relationship status. This is useful for updating UI elements that depend on follow status.

### Batch Following Status Check

When displaying a list of users in your app, you often need to know the following status for multiple users at once. The batch following status endpoint allows you to efficiently check following status for multiple users in a single API call, optimizing performance and reducing network requests.

<CodeGroup>
  ```swift Swift theme={null}
  do {
      // Check following status for multiple users at once
      let userIds = ["user-1", "user-2", "user-3"]
      let statuses = try await ContactsService.shared.socialService.checkBatchFollowingStatus(
          userIds: userIds
      )
      
      // Process the results
      for (userId, status) in statuses {
          print("User \(userId): Following = \(status.isFollowing)")
          if let since = status.since {
              print("Following since: \(since)")
          }
      }
  } catch {
      print("Error checking batch following status: \(error.localizedDescription)")
  }
  ```

  ```jsx React Native theme={null}
  import { checkBatchFollowingStatus } from '@contactsmanager/rn';

  try {
    const userIds = ['user-1', 'user-2', 'user-3'];
    const statuses = await checkBatchFollowingStatus(userIds);
    
    // Process the results
    Object.entries(statuses).forEach(([userId, status]) => {
      console.log(`User ${userId}: Following = ${status.isFollowing}`);
      if (status.since) {
        console.log(`Following since: ${status.since}`);
      }
    });
  } catch (error) {
    console.error('Error checking batch following status:', error);
  }
  ```

  ```kotlin Kotlin theme={null}
  // Coming Soon
  ```

  ```objectivec Objective-C theme={null}
  // Coming Soon
  ```
</CodeGroup>

The `checkBatchFollowingStatus` method:

* Takes an array of user IDs to check
* Returns a dictionary/map where keys are user IDs and values contain:
  * `isFollowing`: Boolean indicating if you're following the user
  * `since`: Optional timestamp indicating when you started following the user
* Optimizes performance by checking multiple users in a single network request
* Useful for implementing efficient UI updates in user lists or grids

### Retrieving Followers and Following

Building social experiences often requires displaying lists of followers or users being followed. These methods provide paginated access to these relationships with rich user information.

<CodeGroup>
  ```swift Swift theme={null}
  // Get followers with pagination
  do {
      let followers = try await ContactsService.shared.socialService.getFollowers(
          skip: 0, 
          limit: 20
      )
      
      print("You have \(followers.total) followers")
      
      // Display followers
      for relationship in followers.items {
          if let follower = relationship.follower {
              print("Follower: \(follower.fullName ?? "Unknown")")
              
              // Access local contact data if available
              if let localContact = relationship.localContact {
                  print("Local contact: \(localContact.displayName ?? "Unknown")")
              }
          }
      }
      
      // Check if there are more followers to load
      if followers.total > followers.limit {
          // Get the next page
          let nextPage = try await ContactsService.shared.socialService.getFollowers(
              skip: followers.limit, 
              limit: 20
          )
      }
  } catch {
      print("Error retrieving followers: \(error.localizedDescription)")
  }

  // Get users the current user is following
  do {
      let following = try await ContactsService.shared.socialService.getFollowing(
          skip: 0,
          limit: 20
      )
      
      print("You are following \(following.total) users")
  } catch {
      print("Error retrieving following: \(error.localizedDescription)")
  }
  ```

  ```jsx React Native theme={null}
  import { getFollowers, getFollowing } from '@contactsmanager/rn';

  // Get followers with pagination
  try {
    const followers = await getFollowers(0, 20);
    
    console.log(`You have ${followers.total} followers`);
    
    // Display followers
    followers.items.forEach(relationship => {
      if (relationship.follower) {
        console.log(`Follower: ${relationship.follower.fullName || 'Unknown'}`);
        
        // Access local contact data if available
        if (relationship.localContact) {
          console.log(`Local contact: ${relationship.localContact.displayName || 'Unknown'}`);
        }
      }
    });
    
    // Check if there are more followers to load
    if (followers.total > followers.limit) {
      // Get the next page
      const nextPage = await getFollowers(followers.limit, 20);
    }
  } catch (error) {
    console.error('Error retrieving followers:', error);
  }

  // Get users the current user is following
  try {
    const following = await getFollowing(0, 20);
    
    console.log(`You are following ${following.total} users`);
  } catch (error) {
    console.error('Error retrieving following:', error);
  }
  ```

  ```kotlin Kotlin theme={null}
  // Get followers with pagination
  try {
      val followers = SocialService.getInstance(context)
          .getFollowers(userId = null, skip = 0, limit = 20)
          .getOrThrow()
      
      println("You have ${followers.total} followers")
      
      // Display followers
      followers.items.forEach { relationship ->
          relationship.follower?.let { follower ->
              println("Follower: ${follower.fullName ?: "Unknown"}")
              
              // Access local contact data if available
              relationship.localContact?.let { localContact ->
                  println("Local contact: ${localContact.displayName ?: "Unknown"}")
              }
          }
      }
      
      // Check if there are more followers to load
      if (followers.total > followers.limit) {
          // Get the next page
          val nextPage = SocialService.getInstance(context)
              .getFollowers(userId = null, skip = followers.limit, limit = 20)
              .getOrThrow()
      }
  } catch (e: Exception) {
      println("Error retrieving followers: ${e.message}")
  }

  // Get users the current user is following
  try {
      val following = SocialService.getInstance(context)
          .getFollowing(userId = null, skip = 0, limit = 20)
          .getOrThrow()
      
      println("You are following ${following.total} users")
  } catch (e: Exception) {
      println("Error retrieving following: ${e.message}")
  }
  ```

  ```objectivec Objective-C theme={null}
  // Get followers with pagination
  [[CMContactService sharedInstance].socialService getFollowersWithUserId:nil 
                                                                    skip:0 
                                                                   limit:20 
                                                              completion:^(CMPaginatedFollowList * _Nullable list, NSError * _Nullable error) {
      if (error) {
          NSLog(@"Error retrieving followers: %@", error.localizedDescription);
          return;
      }
      
      NSLog(@"You have %ld followers", (long)list.total);
      
      // Display followers
      for (CMFollowRelationship *relationship in list.items) {
          if (relationship.follower) {
              NSLog(@"Follower: %@", relationship.follower.fullName ?: @"Unknown");
              
              // Access local contact data if available
              if (relationship.localContact) {
                  NSLog(@"Local contact: %@", relationship.localContact.displayName ?: @"Unknown");
              }
          }
      }
      
      // Check if there are more followers to load
      if (list.total > list.limit) {
          // Get the next page
          [[CMContactService sharedInstance].socialService getFollowersWithUserId:nil 
                                                                           skip:list.limit 
                                                                          limit:20 
                                                                     completion:^(CMPaginatedFollowList * _Nullable nextPage, NSError * _Nullable error) {
              // Handle next page
          }];
      }
  }];

  // Get users the current user is following
  [[CMContactService sharedInstance].socialService getFollowingWithUserId:nil 
                                                                    skip:0 
                                                                   limit:20 
                                                              completion:^(CMPaginatedFollowList * _Nullable list, NSError * _Nullable error) {
      if (error) {
          NSLog(@"Error retrieving following: %@", error.localizedDescription);
          return;
      }
      
      NSLog(@"You are following %ld users", (long)list.total);
  }];
  ```
</CodeGroup>

Both `getFollowers` and `getFollowing` methods:

* Support pagination through `skip` and `limit` parameters
* Return the total number of relationships for implementing UIs with counters
* Provide both server profile data and local contact information when available
* Can be used to create scrollable lists in profile views or dedicated followers/following screens

### Get Mutual Follows

Mutual follows (users who follow the current user and whom the current user follows back) often represent stronger social connections. Displaying mutual follows can help users identify their most engaged connections.

<CodeGroup>
  ```swift Swift theme={null}
  do {
      let mutualFollows = try await ContactsService.shared.socialService.getMutualFollows(
          skip: 0,
          limit: 20
      )
      
      print("You have \(mutualFollows.total) mutual follows")
  } catch {
      print("Error retrieving mutual follows: \(error.localizedDescription)")
  }
  ```

  ```jsx React Native theme={null}
  import { getMutualFollows } from '@contactsmanager/rn';

  try {
    const mutualFollows = await getMutualFollows(0, 20);
    
    console.log(`You have ${mutualFollows.total} mutual follows`);
  } catch (error) {
    console.error('Error retrieving mutual follows:', error);
  }
  ```

  ```kotlin Kotlin theme={null}
  try {
      val mutualFollows = SocialService.getInstance(context)
          .getMutualFollows(skip = 0, limit = 20)
          .getOrThrow()
      
      println("You have ${mutualFollows.total} mutual follows")
  } catch (e: Exception) {
      println("Error retrieving mutual follows: ${e.message}")
  }
  ```

  ```objectivec Objective-C theme={null}
  [[CMContactService sharedInstance].socialService getMutualFollowsWithSkip:0 
                                                                     limit:20 
                                                                completion:^(CMPaginatedMutualFollowers * _Nullable list, NSError * _Nullable error) {
      if (error) {
          NSLog(@"Error retrieving mutual follows: %@", error.localizedDescription);
          return;
      }
      
      NSLog(@"You have %ld mutual follows", (long)list.total);
  }];
  ```
</CodeGroup>

The `getMutualFollows` method returns bidirectional relationships where both users follow each other. This is useful for implementing "close friends" features or prioritizing content from mutual connections.

## Activity Feeds

The SDK provides activity feeds to display content from users. Activity feeds create a dynamic social experience where users can see updates, events, and actions from people they follow. For detailed implementation of feed features and events, please refer to the [Events documentation](./events).

## Building a Social Activity Feed

Here's an example of building a social activity feed with SwiftUI, demonstrating a complete implementation of a scrollable feed with pagination, error handling, and empty states.

> This is a comprehensive example that shows a complete implementation. The core concepts can be adapted to fit your app's design and requirements.

```swift ActivityFeedView.swift [expandable] theme={null}
struct ActivityFeedView: View {
    @State private var feedEvents: [SocialEvent] = []
    @State private var isLoading = true
    @State private var error: Error?
    @State private var currentPage = 0
    @State private var hasMoreEvents = true
    
    private let pageSize = 20
    
    var body: some View {
        NavigationView {
            Group {
                if isLoading && feedEvents.isEmpty {
                    ProgressView("Loading feed...")
                } else if let error = error {
                    VStack {
                        Text("Error loading feed")
                            .font(.headline)
                        Text(error.localizedDescription)
                            .foregroundColor(.red)
                        Button("Try Again") {
                            loadFeed(refresh: true)
                        }
                        .padding()
                    }
                } else if feedEvents.isEmpty {
                    VStack {
                        Image(systemName: "person.2.slash")
                            .font(.system(size: 48))
                            .foregroundColor(.gray)
                            .padding()
                        
                        Text("No events in your feed")
                            .font(.headline)
                        
                        Text("Follow more people to see their events")
                            .foregroundColor(.gray)
                        
                        Button("Find People to Follow") {
                            // Navigate to recommendations view
                        }
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(8)
                        .padding(.top)
                    }
                    .padding()
                } else {
                    List {
                        ForEach(feedEvents, id: \.id) { event in
                            EventRow(event: event)
                        }
                        
                        if hasMoreEvents {
                            ProgressView()
                                .frame(maxWidth: .infinity, alignment: .center)
                                .onAppear {
                                    loadMoreEvents()
                                }
                        }
                    }
                    .refreshable {
                        await refreshFeed()
                    }
                }
            }
            .navigationTitle("Activity Feed")
            .onAppear {
                if feedEvents.isEmpty {
                    loadFeed(refresh: true)
                }
            }
        }
    }
    
    private func loadFeed(refresh: Bool) {
        if refresh {
            currentPage = 0
            feedEvents = []
        }
        
        isLoading = true
        error = nil
        
        Task {
            do {
                let feed = try await ContactsService.shared.socialService.getFeed(
                    skip: currentPage * pageSize,
                    limit: pageSize
                )
                
                await MainActor.run {
                    if refresh {
                        feedEvents = feed.items
                    } else {
                        feedEvents.append(contentsOf: feed.items)
                    }
                    
                    hasMoreEvents = feed.items.count >= pageSize && feed.total > feedEvents.count
                    currentPage += 1
                    isLoading = false
                }
            } catch {
                await MainActor.run {
                    self.error = error
                    self.isLoading = false
                }
            }
        }
    }
    
    private func loadMoreEvents() {
        if !isLoading && hasMoreEvents {
            loadFeed(refresh: false)
        }
    }
    
    private func refreshFeed() async {
        await MainActor.run {
            currentPage = 0
            feedEvents = []
            isLoading = true
            error = nil
        }
        
        do {
            let feed = try await ContactsService.shared.socialService.getFeed(
                skip: 0,
                limit: pageSize
            )
            
            await MainActor.run {
                feedEvents = feed.items
                hasMoreEvents = feed.items.count >= pageSize && feed.total > feedEvents.count
                currentPage = 1
                isLoading = false
            }
        } catch {
            await MainActor.run {
                self.error = error
                self.isLoading = false
            }
        }
    }
}

struct EventRow: View {
    let event: SocialEvent
    
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            // Event header with creator info
            HStack {
                // Avatar placeholder
                Circle()
                    .fill(Color.gray.opacity(0.3))
                    .frame(width: 40, height: 40)
                
                VStack(alignment: .leading) {
                    Text(event.creatorName ?? "Unknown")
                        .font(.headline)
                    
                    Text(formattedEventTime)
                        .font(.caption)
                        .foregroundColor(.gray)
                }
                
                Spacer()
            }
            
            // Event content
            VStack(alignment: .leading, spacing: 4) {
                Text(event.title)
                    .font(.title3)
                    .fontWeight(.bold)
                
                if let description = event.description, !description.isEmpty {
                    Text(description)
                        .font(.body)
                        .lineLimit(3)
                }
                
                if let location = event.location, !location.isEmpty {
                    HStack {
                        Image(systemName: "location.fill")
                            .font(.caption)
                        Text(location)
                            .font(.subheadline)
                    }
                    .foregroundColor(.gray)
                }
            }
            .padding(.vertical, 4)
            
            // Action buttons
            HStack {
                Button(action: {
                    // Add to calendar
                }) {
                    Label("Calendar", systemImage: "calendar.badge.plus")
                }
                .buttonStyle(.bordered)
                
                Spacer()
                
                Button(action: {
                    // Share event
                }) {
                    Label("Share", systemImage: "square.and.arrow.up")
                }
                .buttonStyle(.bordered)
            }
            .padding(.top, 4)
        }
        .padding(.vertical, 8)
    }
    
    private var formattedEventTime: String {
        guard let startTime = event.startTime else {
            return "No date"
        }
        
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formatter.timeStyle = .short
        
        return formatter.string(from: startTime)
    }
}
```

This example demonstrates how to:

* Implement pagination for efficient data loading
* Handle different UI states (loading, error, empty, populated)
* Create visually appealing event cards
* Implement pull-to-refresh functionality
* Automatically load more content when scrolling to the bottom

```jsx React Native ActivityFeedScreen.js [expandable] theme={null}
import React, { useState, useEffect } from 'react';
import {
  View,
  Text,
  FlatList,
  StyleSheet,
  ActivityIndicator,
  TouchableOpacity,
  RefreshControl
} from 'react-native';
import { getFeed } from '@contactsmanager/rn';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';

const ActivityFeedScreen = () => {
  const [feedEvents, setFeedEvents] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [isRefreshing, setIsRefreshing] = useState(false);
  const [error, setError] = useState(null);
  const [currentPage, setCurrentPage] = useState(0);
  const [hasMoreEvents, setHasMoreEvents] = useState(true);
  
  const pageSize = 20;
  
  useEffect(() => {
    if (feedEvents.length === 0) {
      loadFeed(true);
    }
  }, []);
  
  const loadFeed = async (refresh = false) => {
    if (refresh) {
      setCurrentPage(0);
      setFeedEvents([]);
    }
    
    setIsLoading(!isRefreshing);
    setError(null);
    
    try {
      const feed = await getFeed(currentPage * pageSize, pageSize);
      
      if (refresh) {
        setFeedEvents(feed.items);
      } else {
        setFeedEvents(prevEvents => [...prevEvents, ...feed.items]);
      }
      
      setHasMoreEvents(feed.items.length >= pageSize && feed.total > (currentPage * pageSize + feed.items.length));
      setCurrentPage(prev => prev + 1);
    } catch (error) {
      setError(error.message);
    } finally {
      setIsLoading(false);
      setIsRefreshing(false);
    }
  };
  
  const handleRefresh = () => {
    setIsRefreshing(true);
    loadFeed(true);
  };
  
  const loadMoreEvents = () => {
    if (!isLoading && hasMoreEvents) {
      loadFeed(false);
    }
  };
  
  // Format event time
  const formatEventTime = (startTime) => {
    if (!startTime) return 'No date';
    
    const date = new Date(startTime);
    return date.toLocaleString();
  };
  
  // Event row component
  const EventRow = ({ event }) => (
    <View style={styles.eventCard}>
      {/* Event header with creator info */}
      <View style={styles.eventHeader}>
        <View style={styles.avatar} />
        
        <View style={styles.creatorInfo}>
          <Text style={styles.creatorName}>{event.creatorName || 'Unknown'}</Text>
          <Text style={styles.eventTime}>{formatEventTime(event.startTime)}</Text>
        </View>
      </View>
      
      {/* Event content */}
      <View style={styles.eventContent}>
        <Text style={styles.eventTitle}>{event.title}</Text>
        
        {event.description && (
          <Text style={styles.eventDescription} numberOfLines={3}>
            {event.description}
          </Text>
        )}
        
        {event.location && (
          <View style={styles.locationContainer}>
            <Icon name="map-marker" size={14} color="#666" />
            <Text style={styles.locationText}>{event.location}</Text>
          </View>
        )}
      </View>
      
      {/* Action buttons */}
      <View style={styles.actionButtons}>
        <TouchableOpacity style={styles.actionButton}>
          <Icon name="calendar-plus" size={18} color="#007AFF" />
          <Text style={styles.actionButtonText}>Calendar</Text>
        </TouchableOpacity>
        
        <TouchableOpacity style={styles.actionButton}>
          <Icon name="share-variant" size={18} color="#007AFF" />
          <Text style={styles.actionButtonText}>Share</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
  
  // Empty state component
  const EmptyFeed = () => (
    <View style={styles.emptyContainer}>
      <Icon name="account-group-outline" size={48} color="#999" />
      <Text style={styles.emptyTitle}>No events in your feed</Text>
      <Text style={styles.emptySubtitle}>Follow more people to see their events</Text>
      <TouchableOpacity style={styles.findPeopleButton}>
        <Text style={styles.findPeopleButtonText}>Find People to Follow</Text>
      </TouchableOpacity>
    </View>
  );
  
  // Error state component
  const ErrorState = () => (
    <View style={styles.errorContainer}>
      <Text style={styles.errorTitle}>Error loading feed</Text>
      <Text style={styles.errorMessage}>{error}</Text>
      <TouchableOpacity style={styles.retryButton} onPress={() => loadFeed(true)}>
        <Text style={styles.retryButtonText}>Try Again</Text>
      </TouchableOpacity>
    </View>
  );
  
  return (
    <View style={styles.container}>
      {isLoading && feedEvents.length === 0 ? (
        <ActivityIndicator size="large" style={styles.loader} />
      ) : error ? (
        <ErrorState />
      ) : feedEvents.length === 0 ? (
        <EmptyFeed />
      ) : (
        <FlatList
          data={feedEvents}
          renderItem={({ item }) => <EventRow event={item} />}
          keyExtractor={item => item.id}
          contentContainerStyle={styles.feedList}
          refreshControl={
            <RefreshControl refreshing={isRefreshing} onRefresh={handleRefresh} />
          }
          onEndReached={loadMoreEvents}
          onEndReachedThreshold={0.5}
          ListFooterComponent={
            hasMoreEvents && (
              <ActivityIndicator style={styles.footerLoader} size="small" />
            )
          }
        />
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8f8f8',
  },
  loader: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  feedList: {
    padding: 12,
  },
  eventCard: {
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 3,
    elevation: 2,
  },
  eventHeader: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 12,
  },
  avatar: {
    width: 40,
    height: 40,
    borderRadius: 20,
    backgroundColor: 'rgba(0,0,0,0.1)',
  },
  creatorInfo: {
    marginLeft: 12,
  },
  creatorName: {
    fontWeight: '600',
    fontSize: 15,
  },
  eventTime: {
    fontSize: 12,
    color: '#666',
  },
  eventContent: {
    marginBottom: 12,
  },
  eventTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 4,
  },
  eventDescription: {
    fontSize: 15,
    color: '#444',
    marginBottom: 8,
  },
  locationContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  locationText: {
    fontSize: 14,
    color: '#666',
    marginLeft: 4,
  },
  actionButtons: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    borderTopWidth: StyleSheet.hairlineWidth,
    borderTopColor: '#e0e0e0',
    paddingTop: 12,
  },
  actionButton: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 8,
  },
  actionButtonText: {
    color: '#007AFF',
    marginLeft: 6,
    fontSize: 14,
  },
  emptyContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 24,
  },
  emptyTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginTop: 16,
    marginBottom: 8,
  },
  emptySubtitle: {
    fontSize: 15,
    color: '#666',
    textAlign: 'center',
    marginBottom: 20,
  },
  findPeopleButton: {
    backgroundColor: '#007AFF',
    paddingVertical: 12,
    paddingHorizontal: 20,
    borderRadius: 8,
  },
  findPeopleButtonText: {
    color: '#fff',
    fontWeight: '600',
  },
  errorContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 24,
  },
  errorTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  errorMessage: {
    fontSize: 14,
    color: 'red',
    textAlign: 'center',
    marginBottom: 20,
  },
  retryButton: {
    backgroundColor: '#007AFF',
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 8,
  },
  retryButtonText: {
    color: '#fff',
    fontWeight: '600',
  },
  footerLoader: {
    marginVertical: 16,
  },
});

export default ActivityFeedScreen;
```

## Best Practices for Social Features

1. **Handle Network Failures**: Social features rely on network communication, so implement proper error handling with user-friendly error messages and retry options to ensure a smooth experience even with spotty connectivity.

2. **Implement Pagination**: Use skip and limit parameters for better performance with large datasets. Avoid loading the entire dataset at once, which can cause performance issues and waste bandwidth.

3. **Optimize for Offline Use**: Cache social data for a better offline experience. Consider storing the most recent feed items locally so users can view content even without an internet connection.

4. **Follow Privacy Guidelines**: Clearly communicate to users how their data is used. Implement proper permissions for accessing user information and provide clear opt-in/opt-out mechanisms for social features.

5. **Support Pull-to-Refresh**: Implement refreshable lists for up-to-date content, as social feeds frequently change and users expect to be able to check for new content easily.

6. **Pre-fetch Content**: Load the next page of content before the user reaches the end of the list to create a seamless scrolling experience without visible loading indicators.

## Troubleshooting

### Common Issues

1. **Authentication Errors**
   * Ensure the SDK is properly initialized with a valid token before attempting social operations
   * Check that the token hasn't expired, and implement proper token refresh mechanisms
   * Verify that users have the necessary permissions to perform social actions

2. **Missing or Incomplete Data**
   * Verify that the necessary user data has been properly synced before enabling social features
   * Check that users have complete profiles with required fields filled out
   * Ensure proper initialization of the ContactsService before accessing social features

3. **Performance Issues**
   * Implement proper pagination in all list views to avoid loading excessive data
   * Use background fetch for keeping social data up-to-date without blocking the UI
   * Cache commonly accessed data locally to reduce network requests and improve responsiveness
