Как обнаружить android контакт обновлен и синхронизировать c с пожарным магазином - PullRequest
1 голос
/ 25 января 2020

Я пытаюсь получить все android контакты, которые были обновлены.

Я сохраняю на firebase последний добавленный мной идентификатор контакта и последнюю обновленную метку времени

Я использую следующая функция, чтобы вернуть курсор всех обновленных контактов для сравнения с сервером Firebase

private fun getUpdatedContacts(): Cursor? {

    val projection = arrayOf(
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME,
            ContactsContract.Contacts.HAS_PHONE_NUMBER,
            ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP)

    val selection = ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + " > ? AND " +
            ContactsContract.Contacts._ID + "<= ?"

    val selectionArgs = arrayOf(mFireContactDetails!!.lcu_ms.toString(), mFireContactDetails!!.lcid.toString())

    val sortOrder = ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + " ASC"

    return mContentResolver.query(
            ContactsContract.Contacts.CONTENT_URI,
            projection,
            selection,
            selectionArgs,
            sortOrder)
}

Но когда я меняю один контакт в моем телефоне, этот курсор возвращается МНОГИЕ несвязанные контакты, которые я никогда не использовал, и отмечают они как изменились. В прошлый раз, когда я только что добавил номер телефона к существующему контакту, я получил от этого курсора более 50 обновленных контактов.

Что происходит Android ?? Я пытаюсь синхронизировать c контакты за последние 3 месяца. Почему это так сложно ???

Ответы [ 2 ]

2 голосов
/ 26 января 2020

Это почти тот же вопрос с тем же ответом, что и на ваш другой вопрос: При удалении контакта в android, идентификатор другого случайного контакта изменяется

У вас есть некоторые предположения относительно Идентификаторы контактов, которые вы не можете сделать - никто не гарантирует, что идентификаторы контактов являются инкрементными, и никто не гарантирует, что идентификаторы контактов стабильны, на самом деле они определенно не являются.

Вы можете использовать запрашиваемые идентификаторы контактов, пока вы приложение работает, есть очень небольшая вероятность того, что они будут изменены в течение нескольких минут, но есть некоторая вероятность того, что ID для существующих пользователей будут время от времени меняться. Не только это, но и тот же идентификатор может указывать на некоторый контакт сегодня и указывать на совершенно другой контакт завтра.

Если вы сохраните некоторый клон локальных контактов в облаке, вы должны использовать следующие составные идентификаторы для ссылок на контакты: Contacts.CONTACT_ID, Contacts.LOOKUP_KEY, Contacts.DISPLAY_NAME

Подробнее см. мой ответ здесь: Как уникально идентифицировать контакт в таблице ContactsContract.Contacts

Это не идеальное решение, но лучшее, что у нас есть

0 голосов
/ 15 февраля 2020

Я тестировал это решение в течение нескольких дней, и, кажется, все в порядке, но я думаю, что мне нужно протестировать его гораздо больше. Если вы используете этот метод, проведите собственное тестирование и, прежде всего, ПОЖАЛУЙСТА, ДАЙТЕ МНЕ ЗНАТЬ, ЕСЛИ Я ПРОПУСТИТ НИЧЕГО, и не спешите понижать рейтинг. Спасибо!

  1. Я создал класс App, который расширяет приложение и реализует ActivityLifecycleCallbacks. В котором я впервые создаю класс ContactSyn c и активирую его каждый раз, когда приложение выходит на первый план
  2. В классе ContactSyn c я использую Kotlin withContext(Dispatchers.IO), чтобы приостановить любой код для более простой поток
  3. Я использую .get (), чтобы получить все контакты из firestore, связанные с текущим пользователем
  4. в .get () addOnSuccessListener, я добавляю все контакты в HashMap с нормализованным номером телефона в качестве ключа и имени + идентификатор firestore в качестве значений (с использованием внутреннего класса)
  5. При создании HashMap я также проверяю, нет ли дубликатов в firestore с номером телефона smae и, если это так, удалите их (с помощью пакета)
  6. я затем получить все контакты с android телефона. Сначала я сортирую их по NORMALIZED_NUMBER и DISPLAY_NAME (объясню позже)
  7. Сейчас я создаю batchArray с индексом и счетом, чтобы избежать превышения лимита 500
  8. Я начинаю сканирование через курсор контактов ,
  9. Сначала я получаю нормализованный номер, если он недоступен (ноль), я создаю его самостоятельно, используя созданную мной функцию (возможно, нулевое значение возвращается только для телефонных номеров не в правильном формате, не конечно)
  10. Затем я сравниваю нормализованное число с предыдущим значением курсора. Если то же самое я игнорирую, чтобы избежать дубликатов в firestore (помните, что курсор отсортирован по NORMALIZED_NUMBER)
  11. Затем я проверяю, есть ли нормализованное число в HashMap.
  12. Если в HashMap: я сравниваю имя в HashMap с именем курсора. если отличается, я прихожу к выводу, что имя было изменено , и я обновляю контакт пожарного хранилища в пакетном массиве (не забывайте увеличивать счетчик и, если он превышает 500, увеличить индекс). Затем я удаляю нормализованный номер из HashMap, чтобы избежать его удаления позже
  13. Если не в HashMap: я заключаю, что контакт новый , и добавляю его в firestore через пакет
  14. Я перебираю весь курсор до завершения.
  15. Когда курсор завершен, я закрываю его
  16. Все оставшиеся записи, найденные в HashMap, - это те, которые не были найдены в хранилище, поэтому удалены. Я повторяю и удаляю их, используя пакет
  17. syn c выполняется на стороне телефона

Теперь, так как для создания фактического syn c требуется доступ ко всем пользователям, я пользовательские функции firebase в узле. Я создаю 2 функции:

  1. функция, которая срабатывает при создании нового пользователя (подписано по телефону)
  2. функция, которая срабатывает при создании нового документа контакта.

Обе функции сравнивают пользователей с нормализованным числом в документе и, если они совпадают, записывают uid этого пользователя в поле "friend_uid" документа firestore.

Обратите внимание, что вы можете иметь ошибки в эти функции, если вы пытаетесь использовать их в бесплатном плане Firebase. Я предлагаю перейти на план Blaze и ограничить плату до пары долларов. При переходе на Blaze Google также дает вам бесплатные дополнения и позволяет избежать фактического платежа

. На этом синхронизация c завершена. Синхронизация c занимает всего пару секунд

Чтобы отобразить все контакты, которые являются пользователями приложения, запросить все контакты пользователя с "friend_uid", которые не равны нулю.

Некоторые дополнительные примечания:

  1. .get () будет извлекать все контакты каждый раз, когда выполняется синхронизация c. Это может быть много чтения, если пользователь имеет пару сотен контактов. Чтобы свести к минимуму, я использую .get(Source.DEFAULT) при запуске приложения и .get(Source.CACHE) в другое время. Поскольку названия и номера этих документов изменяются только пользователем, я считаю, что в большинстве случаев это не будет проблемой (тестирование продолжается)
  2. Чтобы максимально сократить процесс syn c, я запускаю его, только если какой-либо контакт изменил свою временную метку. Я сохраняю последнюю метку времени в SharedPreferences и сравниваю ее. Я обнаружил, что в основном он сохраняет syn c при быстром повторном открытии приложения.
  3. Я также сохраняю последнего пользователя, вошедшего в систему. При любом изменении пользователя я заново инициализирую контакты текущего пользователя

Некоторый исходный код (все еще тестируется, пожалуйста, дайте мне знать, если есть ошибки):

private fun getContacts(): Cursor? {
    val projection = arrayOf(
            ContactsContract.CommonDataKinds.Phone._ID,
            ContactsContract.CommonDataKinds.Phone.NUMBER,
            ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER,
            ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
            ContactsContract.CommonDataKinds.Phone.CONTACT_LAST_UPDATED_TIMESTAMP)

    //sort by NORMALIZED_NUMBER to detect duplicates and then by name to keep order and avoiding name change
    val sortOrder = ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER + " ASC, " +
            ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"

    return mContentResolver.query(
            ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
            projection,
            null,
            null,
            sortOrder)
}

    private suspend fun syncContactsAsync() = withContext(Dispatchers.IO)  {

    if (isAnythingChanged() || mFirstRun) {

        if (getValues() == Result.SUCCESS) {
            myPrintln("values retrieved success")
        } else {
            myPrintln("values retrieved failed. Aborting.")
            return@withContext
        }

        val cursor: Cursor? = getContacts()

        if (cursor == null) {
            myPrintln("cursor cannot be null")
            mFireContactHashMap.clear()
            return@withContext
        }

        if (cursor.count == 0) {
            cursor.close()
            mFireContactHashMap.clear()
            myPrintln("cursor empty")
            return@withContext
        }

        var contactName: String?
        var internalContact: InternalContact?
        val batchArray = mutableListOf(FirebaseFirestore.getInstance().batch())
        var batchIndex = 0
        var batchCount = 0
        var normalizedNumber:String?
        var prevNumber = ""
        var firestoreId: String

        while (cursor.moveToNext()) {

            normalizedNumber = cursor.getString(COLUMN_UPDATED_NORMALIZED_NUMBER)

            if (normalizedNumber == null) {
                normalizedNumber = cursor.getString(COLUMN_UPDATED_PHONE_NUMBER)
                normalizedNumber = Phone.getParsedPhoneNumber(mDeviceCountryIso,normalizedNumber,mContext)
            }

            //cursor sorted by normalized numbers so if same as previous, do not check
            if (normalizedNumber != prevNumber) {

                prevNumber = normalizedNumber

                contactName = cursor.getString(COLUMN_UPDATED_DISPLAY_NAME)
                internalContact = mFireContactHashMap[normalizedNumber]

                //if phone number exists on firestore
                if (internalContact != null) {

                    //if name changed, update in firestore
                    if (internalContact.name != contactName) {
                        myPrintln("updating $normalizedNumber from name: ${internalContact.name} to: $contactName")
                        batchArray[batchIndex].update(
                                mFireContactRef.document(internalContact.id),
                                FireContact.COLUMN_NAME,
                                contactName)

                        batchCount++
                    }

                    //remove to avoid deletions
                    mFireContactHashMap.remove(normalizedNumber)
                } else {
                    //New item. Insert
                    if (normalizedNumber != mUserPhoneNumber) {
                        myPrintln("adding $normalizedNumber / $contactName")
                        firestoreId = mFireContactRef.document().id

                        batchArray[batchIndex].set(mFireContactRef.document(firestoreId),
                                FireContact(firestoreId, -1, contactName,
                                        cursor.getString(COLUMN_UPDATED_PHONE_NUMBER),
                                        normalizedNumber))
                        batchCount++
                    }
                }

                if (BATCH_HALF_MAX < batchCount ) {

                    batchArray += FirebaseFirestore.getInstance().batch()
                    batchCount = 0
                    batchIndex++
                }
            }
        }

        cursor.close()

        //Remaining contacts not found on cursor so assumed deleted. Delete from firestore
        mFireContactHashMap.forEach { (key, value) ->
            myPrintln("deleting ${value.name} / $key")
            batchArray[batchIndex].delete(mFireContactRef.document(value.id))
            batchCount++
            if (BATCH_HALF_MAX < batchCount ) {
                batchArray += FirebaseFirestore.getInstance().batch()
                batchCount = 0
                batchIndex++
            }
        }

        //execute all batches

        if ((batchCount > 0) || (batchIndex > 0)) {
            myPrintln("committing changes...")
            batchArray.forEach { batch ->
                batch.commit()
            }
        } else {
            myPrintln("no records to commit")
        }

        myPrintln("end sync")


        mFireContactHashMap.clear()
        mPreferenceManager.edit().putLong(PREF_LAST_TIMESTAMP,mLastContactUpdated).apply()

        mFirstRun = false
    } else {
        myPrintln("no change in contacts")
    }
}

private suspend fun putAllUserContactsToHashMap() : Result {

    var result = Result.FAILED

    val batchArray = mutableListOf(FirebaseFirestore.getInstance().batch())
    var batchIndex = 0
    var batchCount = 0

    mFireContactHashMap.clear()

    var source = Source.CACHE

    if (mFirstRun) {
        source = Source.DEFAULT
        myPrintln("get contacts via Source.DEFAULT")
    } else {
        myPrintln("get contacts via Source.CACHE")
    }


    mFireContactRef.whereEqualTo( FireContact.COLUMN_USER_ID,mUid ).get(source)
    .addOnSuccessListener {documents ->

        var fireContact : FireContact

        for (doc in documents) {

            fireContact = doc.toObject(FireContact::class.java)

            if (!mFireContactHashMap.containsKey(fireContact.paPho)) {
                mFireContactHashMap[fireContact.paPho] = InternalContact(fireContact.na, doc.id)
            } else {
                myPrintln("duplicate will be removed from firestore: ${fireContact.paPho} / ${fireContact.na} / ${doc.id}")

                batchArray[batchIndex].delete(mFireContactRef.document(doc.id))

                batchCount++

                if (BATCH_HALF_MAX < batchCount) {
                    batchArray += FirebaseFirestore.getInstance().batch()
                    batchCount = 0
                    batchIndex++
                }
            }
        }

        result = Result.SUCCESS
    }.addOnFailureListener { exception ->
        myPrintln("Error getting documents: $exception")
    }.await()

    //execute all batches
    if ((batchCount > 0) || (batchIndex > 0)) {
        myPrintln("committing duplicate delete... ")
        batchArray.forEach { batch ->
            batch.commit()
        }
    } else {
        myPrintln("no duplicates to delete")
    }

    return result
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...