Contacts permission is a mechanism of mobile OSes that requires explicit user consent before reading the device’s address book. On iOS, access to contacts is handled through CNContactStore, while on Android it uses the Contacts API and runtime permissions system. According to Apple Developer Documentation, 2025, starting with iOS 18 all apps must use the unified Contacts Access API. Proper implementation of the permission request increases the chances of approval by app store moderation.
Key Takeaways
Contacts permission is an operating system mechanism that protects the user’s address book from unauthorized reading by third-party apps. In mobile OSes, contacts are considered sensitive data because they contain names, phone numbers, email addresses, and photos of people in the user’s network.
On iOS, the permission is governed by the Contacts framework and the CNContactStore class. The user sees a system dialog on the first access request, where they can choose to grant or deny access. On Android, protection is built on the runtime permissions system: the app declares READ_CONTACTS in the manifest and requests it at runtime via ActivityResultLauncher or a fragment handling the result.
According to Statista (2025), over 68% of iOS users and 54% of Android users deny contacts access on the first request. This means developers need not only to implement the request correctly but also to explain to the user why access is necessary.
Industry standard — request access only when the functionality is actually needed, not on first launch. This approach reduces denial rates and improves user experience.
In the Apple ecosystem, access to contacts is regulated by the Contacts framework, introduced in iOS 9. The CNContactStore class provides methods for requesting permission and performing read and write operations. On the first call to requestAccess(for:), the system displays a native dialog explaining the reason for access.
Starting with iOS 17, Apple introduced single contact access mode. Users can select one contact from the address book and share it with the app without revealing the entire database. This mode is implemented via CNContactPickerViewController and does not require calling requestAccess(for:).
It is important for developers to understand: if an app requests full access but functionally only needs one contact, App Store moderators may reject the build. According to Apple App Review Guidelines (2025), section 5.1.1 explicitly requires the minimum necessary amount of data.
With the release of iOS 18, Apple tightened requirements for the Privacy Manifest — the privacy.xcprivacy file where developers declare the reason for accessing protected data. For contacts, the key NSContactsUsageDescription is used with localized text displayed in the system dialog.
Without a correct privacy manifest, the app cannot pass App Store Connect moderation. The description text should be specific: not “To improve performance,” but “To find friends by phone number.”
On Android, access to contacts is protected by the READ_CONTACTS permission, which belongs to the dangerous category — it must be requested at runtime, not just during installation. The runtime permissions mechanism was introduced in Android 6.0 (API 23) and remains the primary way to protect sensitive data.
The READ_CONTACTS permission is declared in the manifest via the uses-permission tag, and requested in code through ActivityResultLauncher or a fragment with onRequestPermissionsResult. The user may deny the request or select “Never ask again,” after which the app must properly handle the denial.
Starting with Android 14 (API 34), the behavior of runtime permissions changed: after two consecutive denials, the OS automatically sets the neverAskAgain flag. According to Google Developer Documentation (2024), developers should check the status via shouldShowRequestPermissionRationale before re-requesting.
For reading contacts, Android uses a ContentProvider called ContactsContract. This is a structured database accessible through ContentResolver. Data is organized into several tables: Contacts (contacts), RawContacts (raw records from different accounts), Data (detailed information: phones, emails, addresses).
Querying ContactsContract is done via the URI ContactsContract.Contacts.CONTENT_URI. Developers should request the minimum number of columns and use projection to filter fields — this speeds up query execution and reduces memory consumption.
The practical implementation of requesting contacts access differs on iOS and Android. Below are specific examples in Swift and Kotlin with handling of all possible permission states.
On iOS, the request is made via the requestAccess method of the CNContactStore class. The result is returned in a closure with a boolean value and an optional error. The example below demonstrates the full request cycle with status handling.
import Contacts
let store = CNContactStore()
store.requestAccess(for: .contacts) { granted, error in
if granted {
print("Contacts access granted")
// Contact operations execution
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
try? store.enumerateContacts(with: request) { contact, stop in
print("\(contact.givenName) \(contact.familyName)")
}
} else {
print("Access denied: \(error?.localizedDescription ?? "unknown error")")
}
}
On Android, the request is made via ActivityResultLauncher with the RequestPermission contract. The example below shows working with ContactsContract.ContentProvider after obtaining permission.
val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
val uri = ContactsContract.Contacts.CONTENT_URI
val cursor = contentResolver.query(uri, null, null, null, null)
cursor?.use {
val nameIndex = it.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)
while (it.moveToNext()) {
val name = it.getString(nameIndex)
Log.d("Contacts", "Contact: $name")
}
}
} else {
// Explain the reason for needing access to the user
showRationaleDialog()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestPermissionLauncher.launch(android.Manifest.permission.READ_CONTACTS)
}
Experienced mobile app developers follow a set of proven practices when working with contacts permission. These rules help pass app store moderation and maintain user trust. Following best practices significantly simplifies the publishing and maintenance process.
Never request contacts access on the first app launch. The first request should occur in the context of a specific feature: finding friends, inviting participants, importing contacts. A user who understands the reason for the request agrees 2–3 times more often, according to Apptentive (2024) research.
If the functionality only needs access to a single contact, use CNContactPickerViewController on iOS or an implicit intent ACTION_PICK on Android. These methods do not require prior permission and let users select a record themselves without exposing the entire address book to the app.
The app must properly handle situations where the user denies access. On iOS, check the status via CNContactStore.authorizationStatus(for:) and direct the user to Settings if needed. On Android, use shouldShowRequestPermissionRationale to show additional explanation before re-requesting.
Never show a second dialog immediately after a denial — this is perceived as aggressive and lowers the app’s rating. Best practice: after some time, show a screen with an explanation and a “Go to Settings” button that opens the system permissions screen via Intent. Test the denial scenario on real devices — simulators do not always correctly reproduce the behavior of system permission dialogs.
Frequently Asked Questions
Apps request contacts access for features like finding friends, inviting participants, auto-filling forms, and syncing with a server. Examples: messengers look up contacts by phone number, CRM apps import clients.
Single contact access (iOS 17+) via CNContactPickerViewController lets users pick one contact without revealing the entire address book. Full access allows the app to read all device contacts through CNContactStore. Single contact access is more secure and does not require specifying NSContactsUsageDescription in the privacy manifest.
On iOS, go to Settings — Privacy & Security — Contacts and disable access for the specific app. On Android, open Settings — Apps — select the app — Permissions — Contacts and choose “Deny.”
Privacy Manifest (privacy.xcprivacy file) is a mandatory document for iOS 18+, in which developers declare the reasons for accessing protected data, including contacts. The NSContactsUsageDescription key contains a localized description displayed in the system permission request dialog.
Android classifies READ_CONTACTS as a dangerous permission because it grants access to the user’s personal data. The runtime permissions mechanism, introduced in Android 6.0, requires explicit consent at runtime, not just during app installation.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also