Recommendations

The ContactsManager SDK provides intelligent recommendation features to enhance your app’s social experience. These recommendations help users discover connections in different contexts, like finding users already using your app or suggesting people they might know.

Types of Recommendations

The SDK offers several recommendation types:

  1. Contacts to Invite: Identify contacts who would benefit from using your app
  2. App Users: Find contacts who are already using your app
  3. People You Might Know: Discover potential connections based on shared contacts

Contacts to Invite

Get a list of contacts that would be good to invite to your app:

do {
    let recommendations = try await ContactsService.shared
        .getSharedContactsByUsersToInvite(limit: 10)
    
    for recommendation in recommendations {
        let contact = recommendation.contact
        let score = recommendation.score
        let reason = recommendation.reason
        
        print("\(contact.displayName ?? "Unknown") - Score: \(score) - " + 
              "Reason: \(reason)")
    }
} catch {
    print("Error getting invitation recommendations: \(error.localizedDescription)")
}

Recommendation Scores

Each recommendation includes a score between 0.0 and 1.0 that indicates how relevant the recommendation is. Higher scores indicate stronger recommendations.

App Users

Find contacts who are already using your app:

do {
    let appUsers = try await ContactsService.shared.getContactsUsingApp(limit: 20)
    
    print("Found \(appUsers.count) contacts using the app")
    
    for appUser in appUsers {
        let contact = appUser.contact
        let organizationUserId = appUser.canonicalContact.organizationUserId
        
        print("\(contact?.displayName ?? "Unknown") - " + 
              "User ID: \(organizationUserId)")
    }
} catch {
    print("Error getting app users: \(error.localizedDescription)")
}

Using Organization User IDs

The organizationUserId property allows you to connect the local contact with their server-side identity, which is useful for social features.

People You Might Know

Discover potential connections based on mutual contact information:

do {
    let connections = try await ContactsService.shared.getUsersYouMightKnow(limit: 15)
    
    print("Found \(connections.count) people you might know")
    
    for user in connections {
        print("\(user.fullName) - " + 
              "\(user.email ?? user.phone ?? "No contact info")")
    }
} catch {
    print("Error getting connection recommendations: \(error.localizedDescription)")
}

Building a Recommendation UI

Here’s an example of how to build a recommendations UI using SwiftUI:

struct RecommendationsView: View {
    @State private var inviteRecommendations: [ContactRecommendation] = []
    @State private var appUsers: [LocalCanonicalContact] = []
    @State private var peopleYouMightKnow: [CanonicalContact] = []
    @State private var isLoading = true
    @State private var error: Error?
    
    var body: some View {
        ScrollView {
            if isLoading {
                ProgressView("Loading recommendations...")
                    .padding()
            } else if let error = error {
                VStack {
                    Text("Error loading recommendations")
                        .font(.headline)
                    Text(error.localizedDescription)
                        .foregroundColor(.red)
                    Button("Try Again") {
                        loadRecommendations()
                    }
                    .padding()
                }
            } else {
                VStack(alignment: .leading, spacing: 20) {
                    // App Users Section
                    if !appUsers.isEmpty {
                        Text("People you know using the app")
                            .font(.headline)
                            .padding(.horizontal)
                        
                        ScrollView(.horizontal, showsIndicators: false) {
                            HStack(spacing: 12) {
                                ForEach(appUsers, id: \.canonicalContact.id) { user in
                                    AppUserCard(user: user)
                                }
                            }
                            .padding(.horizontal)
                        }
                    }
                    
                    // People You Might Know Section
                    if !peopleYouMightKnow.isEmpty {
                        Text("People you might know")
                            .font(.headline)
                            .padding(.horizontal)
                        
                        ScrollView(.horizontal, showsIndicators: false) {
                            HStack(spacing: 12) {
                                ForEach(peopleYouMightKnow, id: \.id) { user in
                                    PeopleYouMightKnowCard(user: user)
                                }
                            }
                            .padding(.horizontal)
                        }
                    }
                    
                    // Invite Recommendations Section
                    if !inviteRecommendations.isEmpty {
                        Text("Invite to the app")
                            .font(.headline)
                            .padding(.horizontal)
                        
                        ScrollView(.horizontal, showsIndicators: false) {
                            HStack(spacing: 12) {
                                ForEach(inviteRecommendations, id: \.contact.id) { 
                                    recommendation in
                                    InviteRecommendationCard(
                                        recommendation: recommendation
                                    )
                                }
                            }
                            .padding(.horizontal)
                        }
                    }
                }
            }
        }
        .navigationTitle("Recommendations")
        .onAppear {
            loadRecommendations()
        }
    }
    
    private func loadRecommendations() {
        isLoading = true
        error = nil
        
        Task {
            do {
                // Load all recommendation types in parallel
                async let inviteTask = ContactsService.shared
                    .getSharedContactsByUsersToInvite(limit: 10)
                async let appUsersTask = ContactsService.shared
                    .getContactsUsingApp(limit: 10)
                async let peopleTask = ContactsService.shared
                    .getUsersYouMightKnow(limit: 10)
                
                // Wait for all tasks to complete
                let (invite, users, people) = try await (
                    inviteTask, 
                    appUsersTask, 
                    peopleTask
                )
                
                // Update UI on main thread
                await MainActor.run {
                    self.inviteRecommendations = invite
                    self.appUsers = users
                    self.peopleYouMightKnow = people
                    self.isLoading = false
                }
            } catch {
                await MainActor.run {
                    self.error = error
                    self.isLoading = false
                }
            }
        }
    }
}

// Individual card views for different recommendation types
struct AppUserCard: View {
    let user: LocalCanonicalContact
    
    var body: some View {
        VStack {
            // Avatar
            if let contact = user.contact,
               let imageData = contact.thumbnailImageData,
               let uiImage = UIImage(data: imageData) {
                Image(uiImage: uiImage)
                    .resizable()
                    .scaledToFill()
                    .frame(width: 80, height: 80)
                    .clipShape(Circle())
            } else {
                Circle()
                    .fill(Color.gray.opacity(0.3))
                    .frame(width: 80, height: 80)
                    .overlay(
                        Text(user.contact?.initials ?? "?")
                            .font(.title2)
                    )
            }
            
            // Name
            Text(user.contact?.displayName ?? user.canonicalContact.fullName)
                .font(.subheadline)
                .fontWeight(.medium)
                .lineLimit(1)
            
            // Follow button
            Button("Follow") {
                // Implement follow functionality
            }
            .padding(.horizontal, 16)
            .padding(.vertical, 6)
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(12)
            .font(.caption)
        }
        .frame(width: 100)
        .padding(.vertical)
    }
}

Best Practices

  1. Load Recommendations Asynchronously: Use async/await to load recommendations without blocking the UI
  2. Implement Pagination: For large contact lists, use the limit parameter to paginate results
  3. Handle Permission Changes: Reload recommendations when contact permissions change
  4. Cache Results Temporarily: Avoid excessive API calls by caching results for a short period
  5. Show Loading States: Always show appropriate loading indicators during network operations

Troubleshooting

Common Issues

  1. Empty Recommendations

    • Ensure the user has granted contacts access
    • Verify that contacts have been synced with syncContacts()
    • Check if the user has enough contacts for meaningful recommendations
  2. Missing User Information

    • Make sure contacts have proper phone numbers or email addresses
    • Ensure the server has up-to-date user information
  3. Poor Recommendation Quality

    • Increase the contacts sync frequency
    • Encourage users to complete their profiles
    • Consider implementing a feedback mechanism for recommendations