Search code examples
androidkotlinimportcontacts

Read birthday and name from Google Contacts in Kotlin


I'm developing an app to store informations about events, birthdays in particular. An interesting feature would be to import every birthday found in Google Contacts, displaying it immediately. The app uses a Room DB to keep the contacts in Event Objects (basically, an event is defined by a name, a surname [optional] and a birthdate, plus some other optional parameters)

Since i'm a bit confused about the contacts providers (not every manufacturer uses the Google contacts provider right?) and every answer i found was quite old and doesn't use Kotlin, i'd like to know how to basically complete this function (located in my main activity, where i can easily access the viewmodel and everything else)

  // Import the contacts from Google Contacts
    fun importContacts(): Boolean {
        // No permission. For now, just send an explanation toast
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(this, getString(R.string.missing_permission), Toast.LENGTH_SHORT).show()
            return false
        }

        // Phase 1: get every contact having at least a name and a birthday
        val contacts = getContacts()

        // Phase 2: convert the extracted data in an Event List, verify duplicates
        val events = mutableListOf<Event>()
        loop@ for (contact in contacts) {
            val splitterName = contact.key.split(",")
            var name: String
            var surname = ""
            var date = LocalDate.of(1970,1,1)
            when (splitterName.size) {
                // Not considering surname only contacts
                1 -> name = splitterName[0].trim()
                2 -> {
                    name = splitterName[1].trim()
                    surname = splitterName[0].trim()
                }
                else -> continue@loop
            }
            try { date = LocalDate.parse(contact.value) }
            catch (e: Exception) { continue }
            val event = Event(id = 0, name = name, surname = surname, originalDate = date)

            // Check if the event is duplicate with a query
            if (!homeViewModel.checkExisting(it.key, it.value) == 0) events.add(event)

        }

        // Phase 3: insert the remaining events in the db
        events.forEach {
            homeViewModel.insert(it)
        }

        return true
    }

    // Get the contacts and save them in a list
    private fun getContacts(): Map<String, String> {
        val nameBirth = mutableMapOf<String, String>()
        val resolver: ContentResolver = contentResolver;
        val cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null)

        if (cursor != null) {
            if (cursor.count > 0) {
                while (cursor.moveToNext()) {
                    val name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_ALTERNATIVE))
                    val birth = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.))
                    nameBirth[name] = "???"
                }
            }
        }
        cursor?.close()
        return nameBirth
    }

While i can easily write phase 2 and 3, i'm rather confused about the best way to deal with the first phase. You can assume i already have the contacts permission granted, and all i want is a list of names, surnames and dates i can use to create the objects.


EDIT: so i modified the above code with my progress. I'm now able to take the names, split the names in name and surname, verify the duplicates and insert the contact in my db. The only thing left is retrieving the birthday and manage the fact that the year may not be specified. I'm using a LocalDate object to save the date, as you see.


Solution

  • // Import the contacts from Google Contacts
    fun importContacts(): Boolean {
        // No permission. For now, just send an explanation toast
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(this, getString(R.string.missing_permission), Toast.LENGTH_SHORT).show()
            return false
        }
    
        // Phase 1: get every contact having at least a name and a birthday
        val contacts = getContacts()
    
        // Phase 2: convert the extracted data in an Event List, verify duplicates
        val events = mutableListOf<Event>()
        loop@ for (contact in contacts) {
            // Take the name and split it to separate name and surname
            val splitterName = contact.value[0].split(",")
            var name: String
            var surname = ""
            var date = LocalDate.of(1970,1,1)
            when (splitterName.size) {
                // Not considering surname only contacts, but considering name only
                1 -> name = splitterName[0].trim()
                2 -> {
                    name = splitterName[1].trim()
                    surname = splitterName[0].trim()
                }
                else -> continue@loop
            }
    
            try {
                // Missing year, put 2020 as a placeholder
                var parseDate = contact.value[1]
                if (contact.value[1].length < 8) parseDate = contact.value[1].replaceFirst("-", "2020")
                date = LocalDate.parse(parseDate)
            }
            catch (e: Exception) { continue }
            val event = Event(id = 0, name = name, surname = surname, originalDate = date)
    
            // The duplicate check is performed at entity level
            events.add(event)
        }
    
        // Phase 3: insert the remaining events in the db
        events.forEach {
            homeViewModel.insert(it)
        }
    
        return true
    }
    
    // Get the contacts and save them in a map
    private fun getContacts(): Map<String, List<String>> {
        val nameBirth = mutableMapOf<String, List<String>>()
    
        // Retrieve name and id
        val resolver: ContentResolver = contentResolver
        val cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null)
        if (cursor != null) {
            if (cursor.count > 0) {
                while (cursor.moveToNext()) {
                    val id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID))
                    val name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_ALTERNATIVE))
                    // Retrieve the birthday
                    val bd = contentResolver
                    val bdc: Cursor? = bd.query(ContactsContract.Data.CONTENT_URI, arrayOf(ContactsContract.CommonDataKinds.Event.DATA),
                        ContactsContract.Data.CONTACT_ID + " = " + id + " AND " + ContactsContract.Data.MIMETYPE + " = '" +
                                ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE + "' AND " + ContactsContract.CommonDataKinds.Event.TYPE +
                                " = " + ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY, null, ContactsContract.Data.DISPLAY_NAME
                    )
    
                    if (bdc != null) {
                        if (bdc.count > 0) {
                            while (bdc.moveToNext()) {
                                // Using a list as key will prevent collisions on same name
                                val birthday: String = bdc.getString(0)
                                val person = listOf<String>(name, birthday)
                                nameBirth[id] = person
                            }
                        }
                        bdc.close()
                    }
                }
            }
        }
        cursor?.close()
        return nameBirth
    }
    

    So this is basically the function i was looking for. I'm sure it could be done better, but i had no particular references in Kotlin. It takes the contacts, check their name and their birthday and manages the case of the year missing. Also, i just manage the case of "name only" but not the "only last name" case.