Contacts Authorization
The ContactsManager SDK requires permission to access the user’s device contacts. This guide explains how to request and manage contacts access in your app.Authorization Status
Check the current authorization status using thecontactsAccessStatus property:
let status = ContactsService.shared.contactsAccessStatus
switch status {
case .notDetermined:
print("User has not yet been asked for permission")
case .authorized:
print("Access granted")
case .denied:
print("Access denied")
case .restricted:
print("Access restricted")
}
import { checkAccessStatus } from '@contactsmanager/rn';
// Check access status
const statusCode = await checkAccessStatus();
// Status codes follow the native platform values
// iOS: 0 = notDetermined, 1 = restricted, 2 = denied, 3 = authorized, 4 = limited
// Android: 0 = denied, 1 = authorized
switch (statusCode) {
case 0: // iOS: notDetermined, Android: denied
console.log("Permission not determined yet (iOS) or denied (Android)");
break;
case 3: // iOS: authorized
case 1: // Android: authorized
console.log("Access granted");
break;
case 2: // iOS: denied
console.log("Access denied");
break;
case 1: // iOS: restricted
console.log("Access restricted");
break;
case 4: // iOS: limited
console.log("Limited access granted");
break;
}
// Get authorization service instance
val authService = ContactAuthorizationService.getInstance(context)
// Check current status
when (authService.checkAccessStatus()) {
ContactsAccessStatus.NOT_DETERMINED -> {
println("User has not yet been asked for permission")
}
ContactsAccessStatus.AUTHORIZED -> {
println("Access granted")
}
ContactsAccessStatus.DENIED -> {
println("Access denied")
}
ContactsAccessStatus.RESTRICTED -> {
println("Access restricted")
}
}
CMContactsAuthorizationService *authService = [CMContactsAuthorizationService sharedInstance];
ContactsAccessStatus status = [authService checkAccessStatus];
switch (status) {
case ContactsAccessStatusNotDetermined:
NSLog(@"User has not yet been asked for permission");
break;
case ContactsAccessStatusAuthorized:
NSLog(@"Access granted");
break;
case ContactsAccessStatusDenied:
NSLog(@"Access denied");
break;
case ContactsAccessStatusRestricted:
NSLog(@"Access restricted");
break;
case ContactsAccessStatusLimitedAuthorized:
NSLog(@"Limited access granted");
break;
}
Requesting Access
Request access to contacts with therequestContactsAccess() method:
let granted = await ContactsService.shared.requestContactsAccess()
if granted {
print("Contacts access granted")
// Now you can use contacts features
} else {
print("Contacts access denied")
// Handle the denial case
}
import { requestContactsAccess } from '@contactsmanager/rn';
try {
// Request access
const { granted, status } = await requestContactsAccess();
if (granted) {
console.log("Contacts access granted");
// Now you can use contacts features
} else {
console.log("Contacts access denied");
// Handle the denial case
}
} catch (error) {
console.error("Error requesting contacts access:", error);
}
// Get authorization service instance
val authService = ContactAuthorizationService.getInstance(context)
// Request access
authService.requestAccess(activity) { status, error ->
if (error == null && status == ContactsAccessStatus.AUTHORIZED) {
println("Contacts access granted")
// Now you can use contacts features
} else {
println("Contacts access denied")
// Handle the denial case
}
}
CMContactsAuthorizationService *authService = [CMContactsAuthorizationService sharedInstance];
[authService requestAccessWithCompletion:^(ContactsAccessStatus status, NSError * _Nullable error) {
if (error == nil && status == ContactsAccessStatusAuthorized) {
NSLog(@"Contacts access granted");
// Now you can use contacts features
} else {
NSLog(@"Contacts access denied");
// Handle the denial case
}
}];
Best Practices for Requesting Access
- Explain Why: Before requesting access, explain to users why your app needs contacts access
- Request at the Right Time: Only request access when you actually need it
- Handle Denial Gracefully: Provide a way for users to enable access later if they initially deny it
Handling Access Denial
When access is denied, the SDK provides a SwiftUI alert that guides users to the Settings app:struct ContentView: View {
var body: some View {
VStack {
Text("Your content here")
}
// This will only show when access has been denied
.overlay(ContactsService.shared.settingsAlert)
}
}
import React, { useEffect, useState } from 'react';
import { View, Text, Alert, Linking, Platform } from 'react-native';
import { shouldShowSettingsAlert } from '@contactsmanager/rn';
function ContactsScreen() {
useEffect(() => {
checkSettingsAlert();
}, []);
const checkSettingsAlert = async () => {
try {
const shouldShow = await shouldShowSettingsAlert();
if (shouldShow) {
Alert.alert(
"Contacts Access Required",
"This app needs access to your contacts. Please enable it in your device settings.",
[
{ text: "Cancel", style: "cancel" },
{
text: "Open Settings",
onPress: () => {
if (Platform.OS === 'ios') {
Linking.openURL('app-settings:');
} else {
Linking.openSettings();
}
}
}
]
);
}
} catch (error) {
console.error("Error checking settings alert:", error);
}
};
return (
<View>
<Text>Your content here</Text>
</View>
);
}
// Get authorization service instance
val authService = ContactAuthorizationService.getInstance(context)
// Check if settings alert should be shown
if (authService.shouldShowSettingsAlert(activity)) {
// Show settings alert
authService.showSettingsAlertView(activity)
}
CMContactsAuthorizationService *authService = [CMContactsAuthorizationService sharedInstance];
// Check if settings alert should be shown
if ([authService shouldShowSettingsAlert]) {
// Show settings alert
[authService showSettingsAlertViewFromViewController:viewController];
}
if ContactsService.shared.contactsAccessStatus == .denied {
// Show your custom UI to guide users to settings
}
import { checkAccessStatus, shouldShowSettingsAlert } from '@contactsmanager/rn';
// Check access status
const statusCode = await checkAccessStatus();
// For iOS: 2 = denied, For Android: 0 = denied
if ((Platform.OS === 'ios' && statusCode === 2) ||
(Platform.OS === 'android' && statusCode === 0)) {
// Show your custom UI to guide users to settings
}
// Alternative approach
const shouldShow = await shouldShowSettingsAlert();
if (shouldShow) {
// Show your custom UI to guide users to settings
}
// Get authorization service instance
val authService = ContactAuthorizationService.getInstance(context)
// Check if access is denied
if (authService.checkAccessStatus() == ContactsAccessStatus.DENIED) {
// Show your custom UI to guide users to settings
}
CMContactsAuthorizationService *authService = [CMContactsAuthorizationService sharedInstance];
if ([authService checkAccessStatus] == ContactsAccessStatusDenied) {
// Show your custom UI to guide users to settings
}
Privacy Configuration
In addition to requesting permission at runtime, you must configure your app’sInfo.plist file with a privacy description:
<key>NSContactsUsageDescription</key>
<string>This app needs access to your contacts to provide contact management and social features.</string>
// In iOS/Info.plist
<key>NSContactsUsageDescription</key>
<string>This app needs access to your contacts to provide contact management and social features.</string>
// For Android in android/app/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.READ_CONTACTS" />
// In AndroidManifest.xml
<uses-permission android:name="android.permission.READ_CONTACTS" />
// In Info.plist
<key>NSContactsUsageDescription</key>
<string>This app needs access to your contacts to provide contact management and social features.</string>
Complete Authorization Flow Example
Here’s a complete example showing the authorization flow:import SwiftUI
import ContactsManager
struct ContactsView: View {
@State private var isLoading = false
@State private var contacts: [Contact] = []
@State private var errorMessage: String?
var body: some View {
VStack {
if isLoading {
ProgressView("Loading contacts...")
} else if let error = errorMessage {
Text("Error: \(error)")
.foregroundColor(.red)
Button("Try Again") {
checkContactsAccess()
}
.padding()
} else if contacts.isEmpty {
Text("No contacts found")
.foregroundColor(.gray)
} else {
List(contacts, id: \.id) { contact in
Text(contact.displayName ?? "Unknown")
}
}
}
.navigationTitle("Contacts")
.onAppear {
checkContactsAccess()
}
// Show settings alert if needed
.overlay(ContactsService.shared.settingsAlert)
}
private func checkContactsAccess() {
Task {
let status = ContactsService.shared.contactsAccessStatus
switch status {
case .notDetermined:
// Request access
let granted = await ContactsService.shared.requestContactsAccess()
if granted {
loadContacts()
} else {
errorMessage = "Contacts access denied"
}
case .authorized:
// Already have access, load contacts
loadContacts()
case .denied, .restricted:
// Access denied or restricted
errorMessage = "Please grant contacts access in Settings"
}
}
}
private func loadContacts() {
isLoading = true
errorMessage = nil
Task {
do {
// First ensure contacts are synced
_ = try await ContactsService.shared.syncContacts()
// Then fetch contacts
let fetchedContacts = try await ContactsService.shared.fetchContacts(
fieldType: .any
)
DispatchQueue.main.async {
self.contacts = fetchedContacts
self.isLoading = false
}
} catch {
DispatchQueue.main.async {
self.errorMessage = error.localizedDescription
self.isLoading = false
}
}
}
}
}
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, ActivityIndicator, Button, StyleSheet, Platform } from 'react-native';
import {
requestContactsAccess,
checkAccessStatus,
shouldShowSettingsAlert,
hasContactsReadAccess,
getContacts
} from '@contactsmanager/rn';
function ContactsScreen() {
const [isLoading, setIsLoading] = useState(false);
const [contacts, setContacts] = useState([]);
const [errorMessage, setErrorMessage] = useState(null);
useEffect(() => {
checkContactsAccess();
}, []);
const checkContactsAccess = async () => {
try {
const statusCode = await checkAccessStatus();
// iOS: 0 = notDetermined, Android: already handled in requestAccess
if (Platform.OS === 'ios' && statusCode === 0) {
// Request access
const { granted } = await requestContactsAccess();
if (granted) {
loadContacts();
} else {
setErrorMessage("Contacts access denied");
}
} else if ((Platform.OS === 'ios' && statusCode === 3) ||
(Platform.OS === 'android' && statusCode === 1)) {
// Access already granted
loadContacts();
} else {
// Access denied or restricted
const shouldShow = await shouldShowSettingsAlert();
if (shouldShow) {
setErrorMessage("Please grant contacts access in Settings");
}
}
} catch (error) {
setErrorMessage(`Error checking contacts access: ${error.message}`);
}
};
const loadContacts = async () => {
setIsLoading(true);
setErrorMessage(null);
try {
// Check if we have read access
const hasReadAccess = await hasContactsReadAccess();
if (hasReadAccess) {
// Fetch contacts
const fetchedContacts = await getContacts();
setContacts(fetchedContacts);
setIsLoading(false);
} else {
setErrorMessage("No read access to contacts");
setIsLoading(false);
}
} catch (error) {
setErrorMessage(`Error loading contacts: ${error.message}`);
setIsLoading(false);
}
};
return (
<View style={styles.container}>
{isLoading ? (
<ActivityIndicator size="large" />
) : errorMessage ? (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Error: {errorMessage}</Text>
<Button title="Try Again" onPress={checkContactsAccess} />
</View>
) : contacts.length === 0 ? (
<Text style={styles.emptyText}>No contacts found</Text>
) : (
<FlatList
data={contacts}
keyExtractor={(item) => item.contactId || item.id}
renderItem={({ item }) => (
<Text style={styles.contactName}>{item.displayName || "Unknown"}</Text>
)}
/>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
errorText: {
color: 'red',
marginBottom: 16,
},
emptyText: {
textAlign: 'center',
color: 'gray',
},
contactName: {
fontSize: 16,
paddingVertical: 8,
},
});
export default ContactsScreen;
// Get authorization service instance
val authService = ContactAuthorizationService.getInstance(context)
// Check current status
when (authService.checkAccessStatus()) {
ContactsAccessStatus.NOT_DETERMINED -> {
println("User has not yet been asked for permission")
}
ContactsAccessStatus.AUTHORIZED -> {
println("Access granted")
}
ContactsAccessStatus.DENIED -> {
println("Access denied")
}
ContactsAccessStatus.RESTRICTED -> {
println("Access restricted")
}
}
CMContactsAuthorizationService *authService = [CMContactsAuthorizationService sharedInstance];
ContactsAccessStatus status = [authService checkAccessStatus];
switch (status) {
case ContactsAccessStatusNotDetermined:
NSLog(@"User has not yet been asked for permission");
break;
case ContactsAccessStatusAuthorized:
NSLog(@"Access granted");
break;
case ContactsAccessStatusDenied:
NSLog(@"Access denied");
break;
case ContactsAccessStatusRestricted:
NSLog(@"Access restricted");
break;
case ContactsAccessStatusLimitedAuthorized:
NSLog(@"Limited access granted");
break;
}
Troubleshooting
Common Issues
-
Permission Dialog Not Showing
- Verify that you’ve added the
NSContactsUsageDescriptionkey to your Info.plist - Make sure you’re calling
requestContactsAccess()on the main thread
- Verify that you’ve added the
-
Permission Denied
- Use the settings alert to guide users to enable access in Settings
- Explain the benefits of contacts access clearly
-
Sync Failing After Permission Granted
- Make sure the SDK is properly initialized
- Check for network connectivity issues
- Verify that you’ve registered the contact source