如何检测更新的Android联系人并同步到Firestore

我正在尝试获取所有已更新的android联系人。

我在Firebase上保存了我添加的最后一个联系人ID和上次更新的时间戳

我正在使用下一个函数来检索所有已更新联系人的光标以与Firebase服务器进行比较

right_div = response.xpath('//div[@class="CLASsnAME"]')                  
right_value = right_div.xpath(
    './h3/span[contains(@title,"VALUE")]'
    '/text()'
    ).get() 

但是当我在手机中更改一个联系人时,会返回很多我从未使用过的不相关联系人,并将它们标记为已更改。上一次,当我只是向现有联系人添加电话号码时,我从该游标中获取了50多个已更新的联系人。

Android上发生了什么??我现在正在尝试同步过去三个月的联系人。为什么这么难??

lwnxtb 回答:如何检测更新的Android联系人并同步到Firestore

这是几乎与您的另一个问题相同的问题,答案相同:When deleting a contact on android,other random contacts id's being changed

您对无法确定的联系人ID进行了一些假设-没有人保证联系人ID是递增的,也没有人保证联系人ID稳定,实际上它们绝对不是。

您可以在应用运行时使用查询的联系人ID,极少的机会在几分钟之内即可更改它们,但有时也有可能偶尔更改现有用户的ID。 不仅如此,相同的ID还可以指向今天的某些联系人,而可以指向明天的完全不同的联系人。

如果您将本地联系人的某些副本保留在云中,则应使用以下组合ID来引用联系人: Contacts.CONTACT_ID,Contacts.LOOKUP_KEY,Contacts.DISPLAY_NAME

在此处查看我的答案以获取更多详细信息:How to uniquely identify a contact on ContactsContract.Contacts table

这不是一个完美的解决方案,但这是我们拥有的最好的解决方案

,

我已经对该解决方案进行了几天的测试,似乎还可以,但是我认为我需要对其进行更多的测试。如果您使用这种方法,请进行自己的测试,最重要的是,如果我有任何遗漏,请让我知道,不要着急降级。谢谢!

  1. 我构建了一个App类,该类扩展了Application并实现了 ActivityLifecycleCallbacks。在其中我为创建一个ContactSync类 第一次,并在应用程序每次进入前途时激活它
  2. 在ContactSync类中,我正在使用Kotlin withContext(Dispatchers.IO)暂停任何代码以简化流程
  3. 我使用.get()从Firestore获取与当前用户相关的所有联系人
  4. 在.get()addOnSuccessListener上,我将所有联系人添加到HashMap中,并将规范化的电话号码作为键,并将名称+ firestore ID作为值(使用内部类)
  5. 在制作HashMap时,我还要确保Firestore上没有重复的smae电话号码,如果有,请删除它们(使用批处理)
  6. 然后,我从Android手机检索所有联系人。我先按NORMALIZED_NUMBER和DISPLAY_NAME对其进行排序(稍后说明)
  7. 我现在正在创建一个带有索引和计数的batchArray,以避免超过500个限制
  8. 我开始通过联系人光标进行扫描,
  9. 我首先获取归一化的号码,如果不可用(空),则使用我创建的函数自行创建归一化的号码(可能是因为不正确格式的电话号码(不确定)仅返回空值)
  10. 然后我将归一化的数字与先前的光标值进行比较。如果相同,我会忽略它以避免在Firestore中重复(请记住,光标按NORMALIZED_NUMBER排序)
  11. 然后我检查HashMap中是否已存在标准化的数字。
  12. 如果在HashMap中:我将HashMap中的名称与游标名称进行比较。如果不同,则我推断名称已更改,并更新批处理数组中的Firestore联系人(请记住要增加计数器,如果超过500,则增加索引)。然后,我从HashMap中删除规范化的数字,以避免以后删除它。
  13. 如果不在HashMap中:我认为该联系人是新联系人,并通过批处理将其添加到firestore
  14. 我遍历所有游标直到完成。
  15. 完成光标后,我将其关闭
  16. 在HashMap中找到的所有其余记录都是在Firestore上找不到的记录,因此被删除了。我使用批处理进行迭代并删除
  17. 同步在电话端完成

现在,由于进行实际同步需要所有用户的访问权,因此我在节点中使用了Firebase功能。我创建了2个函数:

  1. 在创建新用户(通过电话签名)时触发的功能
  2. 在创建新的联系人文档时触发的功能。

这两个函数会将用户与文档中的规范化数字进行比较,如果匹配,则将该用户的uid写入Firestore文档的“ friend_uid”字段中。

请注意,如果您尝试在免费的Firebase计划中使用这些功能,则可能会出现错误。我建议更改为Blaze计划,并将收费限制在几美元。通过更改为Blaze,Google还可以为您提供免费的附加功能,并避免实际付款

到此,同步完成。同步仅需几秒钟

要显示该应用程序用户的所有联系人,请使用“ friend_uid”查询所有不为空的用户联系人。

一些注意事项:

  1. .get()将在每次进行同步时检索所有联系人。如果用户有数百个联系人,那么可能会有很多阅读。为了最小化,我在启动应用程序时使用.get(Source.DEFAULT),在其他时间使用.get(Source.CACHE)。由于这些文档的名称和编号仅由用户修改,因此我相信大多数时候(仍在测试中)将不会有问题
  2. 为了最大限度地减少同步过程,我仅在任何联系人更改其时间戳时才启动它。我将最后一个时间戳保存到SharedPreferences并进行比较。我发现当应用重新快速打开时,它主要可以保存同步。
  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,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
}
本文链接:https://www.f2er.com/2714350.html

大家都在问