Offline Functionality

The ContactsManager SDK stores contact data locally on your device, enabling basic offline functionality. This guide explains how contacts are managed in offline scenarios and how synchronization works.

Offline-First Approach

The SDK stores all contact data locally on the device using SwiftData, which means:

  1. All contact data is accessible offline through the local database
  2. Contacts can be viewed and queried even without an internet connection
  3. Changes made while offline are synchronized when connectivity is restored

Working with Contacts Offline

Fetching Contacts

You can fetch contacts from the local database at any time, regardless of connection status:

do {
    // Fetch contacts from local storage
    let contacts = try await ContactsService.shared.fetchContacts()
    print("Retrieved \(contacts.count) contacts from local storage")
} catch {
    print("Error fetching contacts: \(error.localizedDescription)")
}

You can also fetch contacts with specific field types:

do {
    // Fetch only contacts with phone numbers
    let contactsWithPhones = try await ContactsService.shared.fetchContacts(
        fieldType: .phone
    )
    print("Found \(contactsWithPhones.count) contacts with phone numbers")
} catch {
    print("Error fetching contacts: \(error.localizedDescription)")
}

Fetching a Single Contact

To retrieve a specific contact:

do {
    // Fetch a contact from local storage
    if let contact = try await ContactsService.shared.fetchContact(withId: contactId) {
        print("Found contact: \(contact.displayName ?? "Unknown")")
    } else {
        print("Contact not found")
    }
} catch {
    print("Error fetching contact: \(error.localizedDescription)")
}

Synchronization Management

Manual Synchronization

The SDK automatically attempts to synchronize contacts when the app becomes active, when the contact store changes, or when contacts access is granted. You can also trigger manual synchronization:

do {
    // Force a sync with the server
    let syncedCount = try await ContactsService.shared.syncContacts()
    print("Synchronized \(syncedCount) contacts successfully")
} catch {
    print("Synchronization error: \(error.localizedDescription)")
}

Background Synchronization

You can enable background synchronization by calling the following method during app initialization (typically in your AppDelegate or App):

// Enable background synchronization capabilities
ContactsService.shared.enableBackgroundSync()

This registers a background task with the system that will periodically synchronize contacts even when your app is in the background.

Social Features in Offline Mode

Social features rely more heavily on server connectivity, but some operations are queued for later synchronization when offline.

Following Contacts

To follow a contact (will be synchronized when online):

do {
    let result = try await ContactsService.shared.socialService.followContact(
        followedId: contactId,
        contactId: contactId
    )
    
    if let success = result.success, success {
        print("Follow request processed")
    } else if let alreadyFollowing = result.alreadyFollowing, alreadyFollowing {
        print("Already following this contact")
    }
} catch {
    print("Error processing follow request: \(error.localizedDescription)")
}

Offline Limitations for Social Features

While contact data is fully accessible offline, social features have some limitations:

  1. New user discovery: Finding new users requires connectivity
  2. Activity feeds: New remote events can’t be fetched while offline
  3. Follow status: New follow relationships from other users won’t be visible until synchronization

Implementing an Offline-Aware UI

To provide a good user experience, your UI should be aware of potential synchronization states. Here’s a simplified example:

struct ContactListView: View {
    @State private var contacts: [Contact] = []
    @State private var isLoading = false
    
    var body: some View {
        NavigationView {
            VStack {
                if isLoading {
                    ProgressView("Loading contacts...")
                } else {
                    List(contacts, id: \.id) { contact in
                        ContactRow(contact: contact)
                    }
                    .listStyle(PlainListStyle())
                }
            }
            .navigationTitle("Contacts")
            .onAppear {
                loadContacts()
            }
            .refreshable {
                await syncContacts()
            }
        }
    }
    
    private func loadContacts() {
        isLoading = true
        Task {
            do {
                let fetchedContacts = try await ContactsService.shared.fetchContacts()
                
                await MainActor.run {
                    self.contacts = fetchedContacts
                    self.isLoading = false
                }
            } catch {
                print("Error loading contacts: \(error.localizedDescription)")
                await MainActor.run {
                    self.isLoading = false
                }
            }
        }
    }
    
    private func syncContacts() async {
        do {
            try await ContactsService.shared.syncContacts()
            // Reload contacts after sync
            loadContacts()
        } catch {
            print("Error syncing contacts: \(error.localizedDescription)")
        }
    }
}

Troubleshooting Offline Issues

Common Issues

  1. Synchronization not completing

    • Check for connectivity issues
    • Ensure the SDK is properly initialized
    • Verify that authentication tokens are valid
  2. Unexpected data loss

    • Ensure the app isn’t forcefully terminated during sync
    • Verify that device storage isn’t critically low
    • Check for permission changes while offline
  3. Background sync issues

    • Verify that you’ve called enableBackgroundSync() during app initialization
    • Check that your app has the proper background modes enabled in capabilities
    • Ensure you have the “Background Processing” entitlement for your app

Logging and Debugging

The ContactsManager SDK uses the OSLog system for logging important events. To view these logs, use the Console app on macOS with your device connected, and filter for the subsystem “com.contactsmanager”.