You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firebase跨设备用户ID一致但数据检索不一致问题求助

跨设备Firebase认证与Firestore数据一致性问题

问题背景

在基于Compose官方文档的MakeItSo App项目中,将原项目的匿名认证改为仅允许已认证用户进行Firestore读写操作,但出现跨设备数据无法互通的问题:同一账号在不同设备登录后,保存的数据userId一致,但跨设备查询时无法找到对应文档。

数据模型

data class User(
    val id: String = "",
)

data class Client(
    @DocumentId val id: String = "",
    val name: String = "",
    val phone: String = "",
    val city: String = "",
    val userId: String = ""
)

当前Firestore规则

rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.time < timestamp.date(2023, 12, 8);
    }
  }
}

问题现象

  • 同一账号在两台设备登录,Firebase返回的UID相同,保存的Client数据userId均为xxx
  • 跨设备查询时,无法获取到另一设备保存的对应userId的Client文档

相关代码实现

AccountServiceImpl

class AccountServiceImpl @Inject constructor(
    private val auth: FirebaseAuth
) : AccountService {
    override val currentUserId: String
        get() = auth.currentUser?.uid.orEmpty()

    override val hasUser: Boolean
        get() = auth.currentUser != null

    override val currentUser: Flow<User>
        get() = callbackFlow {
            val listener =
                FirebaseAuth.AuthStateListener { auth ->
                    this.trySend(auth.currentUser?.let { User(it.uid) } ?: User())
                }
            auth.addAuthStateListener(listener)
            awaitClose { auth.removeAuthStateListener(listener) }
        }

    override suspend fun authenticate(email: String, password: String) {
        auth.signInWithEmailAndPassword(email, password).await()
    }

    override suspend fun signOut() {
        auth.signOut()
    }
}

StorageServiceImpl

class StorageServiceImpl @Inject constructor(
    private val firestore: FirebaseFirestore,
    private val auth: AccountService
) : StorageService {

    @OptIn(ExperimentalCoroutinesApi::class)
    override val clients: Flow<List<Client>>
        get() = auth.currentUser.flatMapLatest { user ->
            firestore.collection(CLIENTS_COLLECTION)
                .whereEqualTo(USER_ID_FIELD, user.id)
                .orderBy(ORDER_CLIENTS_BY)
                .dataObjects()
        }


    override suspend fun getClient(clientId: String): Client? =
        firestore.collection(CLIENTS_COLLECTION).document(clientId).get().await().toObject()


    override suspend fun saveClient(client: Client): String {
        val clientWithUserId = client.copy(userId = auth.currentUserId)
        return firestore.collection(CLIENTS_COLLECTION).add(clientWithUserId).await().id
    }

    override suspend fun updateClient(client: Client) {
        firestore.collection(CLIENTS_COLLECTION).document(client.id).set(client).await()
    }

    override suspend fun deleteClient(clientId: String) {
        firestore.collection(CLIENTS_COLLECTION).document(clientId).delete().await()
    }

    companion object {
        const val CLIENTS_COLLECTION = "clients"
        const val ORDER_CLIENTS_BY = "name"
        const val USER_ID_FIELD = "userId"
    }
}

需求

确保已认证用户ID唯一,并实现跨设备数据检索的一致性。


解决方案

1. 修正Firestore安全规则

当前规则仅通过时间限制访问,未验证用户身份且截止日期已过,导致实际无合法访问权限。替换为以下规则,确保仅已认证用户能读写自己的数据:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /clients/{client} {
      // 仅允许已认证用户读取自己的客户端数据
      allow read: if request.auth != null && resource.data.userId == request.auth.uid;
      // 仅允许已认证用户创建/更新属于自己的客户端数据
      allow write: if request.auth != null && request.resource.data.userId == request.auth.uid;
    }
  }
}

2. 检查查询与数据同步逻辑

  • 强制从服务器获取数据:避免本地缓存导致的查询不一致,修改clients Flow的查询逻辑:

    @OptIn(ExperimentalCoroutinesApi::class)
    override val clients: Flow<List<Client>>
        get() = auth.currentUser.flatMapLatest { user ->
            flow {
                val snapshot = firestore.collection(CLIENTS_COLLECTION)
                    .whereEqualTo(USER_ID_FIELD, user.id)
                    .orderBy(ORDER_CLIENTS_BY)
                    .get(Source.SERVER) // 强制从服务器拉取数据
                    .await()
                emit(snapshot.toObjects(Client::class.java))
            }
        }
    

    若需实时监听,确保snapshotListener优先同步服务器数据,或调整Firestore缓存策略。

  • 确保更新时userId不被覆盖:在updateClient方法中强制保留当前用户的userId,避免更新时传入错误值:

    override suspend fun updateClient(client: Client) {
        val clientWithCorrectUserId = client.copy(userId = auth.currentUserId)
        firestore.collection(CLIENTS_COLLECTION).document(client.id).set(clientWithCorrectUserId).await()
    }
    

3. 验证用户认证状态的实时性

  • 确认currentUser Flow正确监听AuthState变化,确保用户登录后及时更新用户ID,避免使用空字符串或旧ID查询。
  • 在查询和数据操作前添加非空校验,例如判断auth.currentUserId是否为空,避免无效操作。

4. 检查Firestore数据存储

直接在Firebase控制台查看clients集合的文档,确认userId字段与用户UID完全一致(注意大小写、空格等细节,Firestore字段值区分大小写)。


内容的提问来源于stack exchange,提问作者Collaxd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 18:20:05