react-native-contacts: Contacts.getAll() crashes Android app when permissions are not granted

Expected Behaviour:

If permissions are not granted, the callback should be called, with the error field being non-null/undefined.

    Contacts.getAll((err, contacts) => {
        // err should be defined
    })

Actual Behaviour:

App crashes completely. React Native does not even have the chance to catch the error.

My code:

async function requestPermissions() {
  let status = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_CONTACTS)
  console.log('status', status)
}

async function contactsGetAll(): Promise<Contact[]> {
  await requestPermissions()
  console.log('permissions')
  return new Promise((res, rej) => {
    Contacts.getAll((err, contacts) => {
      console.log('err', err)
      if (err) {  rej(err) } 
     else { res(contacts) }
    })
  })
}

contacts: Contact[] = await contactsGetAll()

console.log output:

status: never_ask_again
permissions
// crashes

Workaround fix:

Check permissions before calling Contacts.getAll()

  let status = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_CONTACTS)
  if (status === 'denied' || status === 'never_ask_again') {
    throw Error('Permissions not granted to access Contacts')
  }
Contacts.getAll((err, contacts) => {})

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 5
  • Comments: 15 (2 by maintainers)

Most upvoted comments

I’m not sure if there’s anything we can/should do about this. Checking for permissions before performing privileged actions seems fine to me.

in android manifest file use

<uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission
        android:name="android.permission.READ_PROFILE"
        android:maxSdkVersion="22"
        tools:targetApi="donut" />
<uses-permission tools:node="remove" android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_PROFILE" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />

i do not understand why i need READ_PROFILE ?

Btw, just sharing my code if anyone just wants a quick copy and paste:

Usage:

import { Contacts } from './Contacts'

Contacts.getAll().then(contacts => {
  contacts.forEach(contact => {
    console.log(contact)
    console.log(contact.givenName)
    console.log(contact.phoneNumbers)
  })
})

Contacts.ts

// Wrapper for permissions as original function does not throw
const requestPermissions = async (): Promise<void> => {
  // If app would like to display the rational for requesting permissions,
  // then place it as second param in PermissionsAndroid.request().
  if (Platform.OS === 'android') {
    let status = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_CONTACTS)
    if (status === 'denied' || status === 'never_ask_again') {
      throw Error('Permission not granted to access contacts')
    }
  }
}

// Wrapper for requesting contacts as original function returns callback instead of Promise
const getAll = async (): Promise<Contact[]> => {
  await requestPermissions()
  return new Promise((res, rej) => {
    RNContacts.getAll((err, contacts) => {
      if (err) {
        rej(Error('Permission not granted to access contacts'))
      } else {
        res(contacts)
      }
    })
  })
}

export const Contacts = { getAll }

I’m not sure if there’s anything we can/should do about this. Checking for permissions before performing privileged actions seems fine to me.

There is inconsistent behaviour on Android vs iOS though.

In iOS, permissions aren’t granted, it will be handled in the error block:

    Contacts.getAll((err, contacts) => {
        // iOS permission errors will be caught here
    })

In Android, if permissions are not granted, errors do not get caught by this block. It crashes the entire app entirely.


  1. Why provide an error handler for when the error handler only catches iOS errors and not Android errors? It’s misleading to developers to see an error handler block that does not catch errors. I feel it would be better if both Android & iOS were handled the same way.

  2. It does not seem appropriate that calling a ReactNative function can cause an entire Android app to crash. I feel errors should be handled at the ReactNative boundary.

I solved it by the using the below piece of “try catch” code logic:

The app will not crash, since all the cases are handled .


try {
        const andoidContactPermission = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
          {
            title: "Contacts Permission",
            message:
              "This app would like to view your contacts.",
            buttonNeutral: "Ask Me Later",
            buttonNegative: "Cancel",
            buttonPositive: "OK"
          }
        );
        if (andoidContactPermission === PermissionsAndroid.RESULTS.GRANTED) {
          console.log("Contacts Permission granted");
          Contacts.getAll((andoidContactPermission, contacts) => {
              console.log(contacts);
          });
        } else {
          console.log("Contacts permission denied");
        }
      } catch (err) {
        console.log(err);
 }
    
    

Hope this helps 👍

i’m on version 7.0.7 and adding <uses-permission android:name="android.permission.READ_CONTACTS" /> fixed my issue on react-native 0.70.6

I solved it by the using the below piece of “try catch” code logic:

The app will not crash, since all the cases are handled .

try {
        const andoidContactPermission = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
          {
            title: "Contacts Permission",
            message:
              "This app would like to view your contacts.",
            buttonNeutral: "Ask Me Later",
            buttonNegative: "Cancel",
            buttonPositive: "OK"
          }
        );
        if (andoidContactPermission === PermissionsAndroid.RESULTS.GRANTED) {
          console.log("Contacts Permission granted");
          Contacts.getAll((andoidContactPermission, contacts) => {
              console.log(contacts);
          });
        } else {
          console.log("Contacts permission denied");
        }
      } catch (err) {
        console.log(err);
 }
    
    

Hope this helps 👍

The newer way of calling Contacts.getAll() is now as follows:

        Contacts.getAll()
          .then(contacts => {
            // work with contacts
            console.log(contacts);
          })
          .catch(e => {
            console.log(e);
          });