capacitor-community / contacts

Contacts Plugin for Capacitor
https://capacitor-community.github.io/contacts/
125 stars 54 forks source link

[android] checkPermissions always return 'denied' #129

Open steadev opened 2 months ago

steadev commented 2 months ago

version: 6.0.6

Describe the bug A clear and concise description of what the bug is.

checkPermissions always return 'denied', even though permission granted.

requestPermissions returns 'granted'..

and for checkPermissions, ios works fine

hamzamac commented 2 months ago

I also have a problem on my app using the very same package version, it crashes in production when attempting to access contacts. I my case it crashes before asking user for permission. The Debug version works fine though. I have not investigated it might be it crashes for the same reasons as yours. iOS works fine also for me.

hamzamac commented 2 months ago

I see there is a new version 6.1.1 I will test that.

hamzamac commented 2 months ago

Upgrading to version 6.1.1 did not fix my problem

hamzamac commented 2 months ago

the problem still exists

shaman79 commented 3 weeks ago

I have exactly the same problem. On Android the app does not even try to open permission dialog. Strange thing is that even when I completely remove my app and install it again, it does not even ask for permissions and automatically fails as denied. In the app details on Android I see that the permission is denied. If anyone knows how to fix this it would be highly appreciated.

shaman79 commented 1 week ago

Can someone from the dev team please let us know if this is going to be fixed? Thanks.

tafelnl commented 1 week ago

We're using the helpers provided by the Capacitor code itself. We don't do any logic on this. Are you sure you added the necessary lines in AndroidManifest.xml? Could you please share a reproduction

shaman79 commented 1 week ago

Yes, I added the permissions to the manifest file, checked everything 20 times. It does not make any sense. Maybe the helpers are not working ok in later versions of Android. I am going to check that too.

Below is the complete service class we are using.

import axios from 'axios'
import responseInterceptor from './response.interceptor'
import authInterceptor from './auth.interceptor'
import {Contacts} from '@capacitor-community/contacts'
import {storage} from './storage'
import {generateAvatarInitials} from '../utils/generateAvatarInitials'
import {store} from '../store'
const API_URL = import.meta.env.VITE_API_URL

class ContactService {
    api

    constructor() {
        this.api = axios.create({baseURL: API_URL})
        authInterceptor(this.api)
        responseInterceptor(this.api)
    }

    async fetchContactsFromPhone(term) {
        try {
            const perms = await Contacts.getPermissions()

            if (perms.granted || perms.contacts === 'granted') {
                console.debug('ContactService.fetchContactsFromPhone: Permissions granted')
                const {contacts} = await Contacts.getContacts()
                console.debug('ContactService.fetchContactsFromPhone: fetched contacts:')
                console.debug(contacts)
                term = term.toLowerCase()

                return contacts
                    .map(this.mapLocalContactToContact)
                    .filter(c => c.fullname?.length)
                    .filter(c => {
                        if (!term?.length) {
                            return true
                        }

                        return c.fullname?.toLowerCase().includes(term) ||
                            c.phone?.toLowerCase().includes(term) ||
                            c.phone?.replace(/ /g, '').toLowerCase().includes(term) ||
                            c.email?.toLowerCase().includes(term)
                    })
            } else {
                console.warn('ContactService.fetchContactsFromPhone: Permissions not granted')
                console.warn(perms)
            }
        } catch (e) {
            if (e.code === 'UNIMPLEMENTED') {
                console.error('Cannot fetch local contacts from the device.')
            } else {
                console.error(e)
            }
        }

        console.warn('ContactService.fetchContactsFromPhone: Returning empty array')
        return []
    }

    async fetchListCached() {
        return await storage.getJSON('contacts') || []
    }

    async fetchList(filter, term, page, options) {
        let url = `contacts?page=${page}&filter=${encodeURIComponent(JSON.stringify(filter))}`
        url = options?.limit ? url += `&limit=${options.limit}` : url
        url = options?.sort ? url += `&sort=${options.sort}` : url
        url = options?.sortDir ? url += `&sortDir=${options.sortDir}` : url

        let res

        try {
            const response = await this.api.get(url)
            res = response.data
            res.items = res.items
                .map(i => {
                    if (!i.fullname?.trim().length) {
                        i.fullname = i.phone || i.email
                    }
                    return i
                })
                .filter(i => i.fullname?.length)
        } catch (e) {
            if (e.response?.status === 401) {
                store.commit('logout')
            }
            console.error(e)
            //return []
        }

        const localContacts = await this.fetchContactsFromPhone(term)
        // console.log('local contacts')
        // console.log(localContacts)

        res.items = [...localContacts, ...res.items]
            .map(i => {
                i.avatar = generateAvatarInitials(i.fullname || i.email)
                i.sort = i.firstname || i.surname || i.fullname || i.phone || i.email
                return i
            })
            .filter(i => i.fullname?.length)
            .sort((a, b) => a.sort?.toString().localeCompare(b.sort))

        storage.setJSON('contacts', res.items)

        return res
    }

    async fetchItem(id, isLocal) {
        let contact
        if (isLocal) {
            const {contacts} = await Contacts.getContacts()
            contact = contacts.find(i => i.contactId === id)
            if (contact) {
                return this.mapLocalContactToContact(contact)
            }
        } else {
            contact = await  this.api.get(`contacts/${id}`).then(response => response.data)
        }

        contact.avatar = generateAvatarInitials(contact.fullname || contact.email)
        return contact
    }

    mapLocalContactToContact(i) {
        const firstPhoneNumber = i.phoneNumbers?.length ? i.phoneNumbers[0].number : null
        const firstEmail = i.emails?.length ? i.emails[0].address : null
        return {
            _id: i.contactId,
            local: true,
            fullname: i.displayName || firstPhoneNumber || firstEmail || null,
            phone: firstPhoneNumber,
            email: firstEmail,
            company: i.company || null
        }
    }

    async createNewContact(data) {
        const {data: res} = await this.api.post('/contacts', data)
        return res
    }

    async updateContact(id, data) {
        const {data: res} = await this.api.put(`contacts/${id}`, data)
        return res
    }
    async deleteContact(id) {
        return await this.api.delete(`contacts/${id}`)
    }
}

export default new ContactService()
tafelnl commented 1 week ago

Without a reproduction we won't be able to help you unfortunately