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

使用Realm SDK从MongoDB Atlas获取数据返回空响应问题排查求助

问题:Google One Tap登录+JWT认证下,Realm SDK从MongoDB Atlas获取日记数据返回空响应

我的Android应用采用Google One Tap登录与JWT认证,通过Realm SDK从MongoDB Atlas拉取日记数据时始终得到空响应。已经尝试过删除重建数据库、重启Realm同步等操作,问题仍未解决。


Diary模型

package com.krish.diaryapp.model

import io.realm.kotlin.ext.realmListOf
import io.realm.kotlin.types.ObjectId
import io.realm.kotlin.types.RealmInstant
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.PrimaryKey

open class Diary : RealmObject {
    @PrimaryKey
    var _id: ObjectId = ObjectId.create()
    var ownerId: String = ""
    var mood: String = Mood.Neutral.name
    var title: String = ""
    var description: String = ""
    var images: RealmList<String> = realmListOf()
    var date: RealmInstant = RealmInstant.from(System.currentTimeMillis(), 0)
}

RequestState密封类

package com.krish.diaryapp.util

sealed class RequestState<out T> {
    object Idle : RequestState<Nothing>()
    object Loading : RequestState<Nothing>()
    data class Success<T>(val data: T) : RequestState<T>()
    data class Error(val error: Throwable) : RequestState<Nothing>()
}

MongoRepository接口

package com.krish.diaryapp.data.repository

import com.krish.diaryapp.model.Diary
import com.krish.diaryapp.util.RequestState
import kotlinx.coroutines.flow.Flow
import java.time.LocalDate

typealias Diaries = RequestState<Map<LocalDate, List<Diary>>>

interface MongoRepository {
    fun configureTheRealm()
    fun getAllDiaries(): Flow<Diaries>
}

MongoDB实现类

package com.krish.diaryapp.data.repository

import android.util.Log
import com.krish.diaryapp.model.Diary
import com.krish.diaryapp.util.Constants.APP_ID
import com.krish.diaryapp.util.RequestState
import com.krish.diaryapp.util.toInstant
import io.realm.kotlin.Realm
import io.realm.kotlin.ext.query
import io.realm.kotlin.log.LogLevel
import io.realm.kotlin.mongodb.App
import io.realm.kotlin.mongodb.sync.SyncConfiguration
import io.realm.kotlin.query.Sort
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import java.time.ZoneId

object MongoDB : MongoRepository {
    private val app = App.create(APP_ID)
    private val user = app.currentUser
    private lateinit var realm: Realm

    init {
        configureTheRealm()
    }

    override fun configureTheRealm() {
        if (user != null) {
            val config = SyncConfiguration.Builder(user, setOf(Diary::class))
                .initialSubscriptions { sub ->
                    add(
                        query = sub.query<Diary>(query = "ownerId == $0", user.identity),
                        name = "User's Diaries"
                    )
                }
                .log(LogLevel.ALL)
                .build()
            realm = Realm.open(config)
        }
    }

    override fun getAllDiaries(): Flow<Diaries> {
        return if (user != null) {
            try {
                realm.query<Diary>(query = "ownerId == $0", user.identity)
                    .sort(property = "date", sortOrder = Sort.DESCENDING)
                    .asFlow()
                    .map { result ->
                        RequestState.Success(
                            data = result.list.groupBy {
                                it.date.toInstant()
                                    .atZone(ZoneId.systemDefault())
                                    .toLocalDate()
                            }
                        )
                    }
            } catch (e: Exception) {
                flow { emit(RequestState.Error(e)) }
            }
        } else {
            flow { emit(RequestState.Error(UserNotAuthenticatedException())) }
        }
    }
}

private class UserNotAuthenticatedException : Exception("User is not Logged in.")

HomeViewModel

package com.krish.diaryapp.presentation.screens.home

import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.krish.diaryapp.data.repository.Diaries
import com.krish.diaryapp.data.repository.MongoDB
import com.krish.diaryapp.util.RequestState
import kotlinx.coroutines.launch

class HomeViewModel : ViewModel() {
    var diaries: MutableState<Diaries> = mutableStateOf(RequestState.Idle)

    init {
        observeAllDiaries()
    }

    private fun observeAllDiaries() {
        viewModelScope.launch {
            MongoDB.getAllDiaries().collect { result ->
                diaries.value = result
            }
        }
    }
}

MongoDB Atlas Schema

{
  "title": "Diary",
  "bsonType": "object",
  "required": [
    "_id",
    "ownerId",
    "mood",
    "title",
    "description",
    "date"
  ],
  "properties": {
    "_id": {
      "bsonType": "objectId"
    },
    "date": {
      "bsonType": "date"
    },
    "images": {
      "bsonType": "array",
      "items": {
        "bsonType": "string"
      }
    },
    "description": {
      "bsonType": "string"
    },
    "mood": {
      "bsonType": "string"
    },
    "ownerId": {
      "bsonType": "string"
    },
    "title": {
      "bsonType": "string"
    }
  }
}

已完成的排查操作

  • 配置MongoDB Atlas数据库访问规则
  • 查看查询日志确认请求状态
  • 确认集合中存在目标用户的日记数据
  • 尝试删除重建数据库、重启Realm同步服务

排查方向与解决方案

1. 验证User Identity与ownerId匹配

检查Atlas集合中ownerId字段的值,是否和user.identity完全一致(包括大小写、空格、格式)。可以在代码中添加日志输出对比:

Log.d("MongoDB_DEBUG", "当前用户Identity: ${user?.identity}")

2. 修复Realm订阅同步逻辑

当前订阅配置未等待远程数据同步完成,添加waitForInitialRemoteData()确保启动时拉取完所有数据:

val config = SyncConfiguration.Builder(currentUser, setOf(Diary::class))
    .initialSubscriptions { sub ->
        add(
            query = sub.query<Diary>(query = "ownerId == $0", currentUser.identity),
            name = "User's Diaries"
        )
    }
    .waitForInitialRemoteData() // 新增:等待远程数据同步完成
    .log(LogLevel.ALL)
    .build()

3. 修正Realm实例初始化时机

单例初始化时currentUser可能未赋值(Google登录是异步操作),改为实时获取用户并在登录成功后主动初始化:

override fun configureTheRealm() {
    val currentUser = app.currentUser
    if (currentUser != null && currentUser.isLoggedIn) {
        // 原有配置逻辑
    }
}

// 在Google One Tap登录成功的回调中调用:
MongoDB.configureTheRealm()

4. 检查数据库访问规则权限

确保Atlas规则允许用户查询自己的数据,规则示例:

{
  "read": {"ownerId": "%%user.id"},
  "write": {"ownerId": "%%user.id"}
}

注意%%user.id要和你存储的ownerId来源匹配(如果用的是Google用户的identity,需调整规则变量)。

5. 排查日期分组逻辑是否导致空结果

在查询结果中添加日志,确认原始数据数量:

.map { result ->
    Log.d("MongoDB_DEBUG", "拉取到的日记数量: ${result.list.size}")
    RequestState.Success(
        data = result.list.groupBy {
            it.date.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDate()
        }
    )
}

如果原始数量大于0但分组后为空,检查toInstant()扩展函数的实现是否正确。

6. 验证用户认证状态

确保app.currentUser是已登录的有效用户,在查询前增加状态判断:

override fun getAllDiaries(): Flow<Diaries> {
    val currentUser = app.currentUser
    return if (currentUser != null && currentUser.isLoggedIn) {
        // 原有查询逻辑
    } else {
        flow { emit(RequestState.Error(UserNotAuthenticatedException())) }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 10:00:03