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

问题描述 投票:0回答:2

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

我正在将最新添加的联系人ID和最新更新的时间戳保存在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发生了什么?我现在尝试同步过去三个月的联系人。为什么这么难???

我正在尝试获取所有已更新的android联系人。我在Firebase上保存了我添加的最后一个联系人ID和上次更新的时间戳,我正在使用下一个函数来返回游标...

这几乎是与您的另一个问题具有相同答案的相同问题: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类第一次,并在每次进入前途时激活它
  • 在ContactSync类中,我正在使用Kotlin withContext(Dispatchers.IO)暂停任何代码以简化流程
  • 我使用.get()从Firestore获取与当前用户相关的所有联系人
  • 在.get()addOnSuccessListener上,我将所有联系人添加到HashMap中,并将规范化的电话号码作为键,并使用名称+ firestore ID
  • 作为值(使用内部类)
  • [同时制作HashMap时,我还要确保Firestore上没有重复的smae电话号码,如果有,请删除它们(使用批处理)
  • i然后从android手机检索所有联系人。我先按NORMALIZED_NUMBER和DISPLAY_NAME对其进行排序(稍后说明)
  • 我现在正在创建一个带有索引和计数的batchArray,以避免超过500个限制
  • 我开始通过联系人光标进行扫描,
  • 我首先获得归一化的号码,如果不可用(空),则使用我创建的函数自行创建归一化的号码(可能是仅针对不正确格式的电话号码返回了空值,不确定)
  • 然后我将归一化的数字与先前的游标值进行比较。如果相同,我将忽略它以避免在Firestore中重复(请记住,光标按NORMALIZED_NUMBER排序)
  • 然后,我检查HashMap中是否已存在标准化的数字。
  • 如果在HashMap中:我将HashMap中的名称与游标名称进行比较。如果不同,我认为名称已更改
  • ,然后更新批处理数组中的Firestore联系人(记住要增加计数器,如果超过500,则增加索引)。然后,我从HashMap删除规范化的数字,以避免以后删除它。
  • [如果不在HashMap中:我认为联系是新的
  • ,然后通过批处理将其添加到firestore中
  • 我遍历所有游标直到完成。
  • 完成光标后,我将其关闭
  • 在HashMap中找到的所有其余记录都是在Firestore上找不到的记录,因此被删除。
  • 我使用批处理进行迭代并删除它们
  • 在手机端完成同步
  • 现在,由于进行实际同步需要所有用户的访问权限,因此我使用.node作为用户的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. 我还将保存上次登录的用户。如果用户有任何更改,我将重新初始化当前用户联系人
    4. 某些源代码:

      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
      }
      
    android google-cloud-firestore synchronization android-contacts
    2个回答
    2
    投票

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


    0
    投票

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

    1. 我建立了一个App类来扩展Application并实现ActivityLifecycleCallbacks。在其中我为创建一个ContactSync类第一次,并在每次进入前途时激活它
    © www.soinside.com 2019 - 2024. All rights reserved.