Reputation: 11
I am encountering an issue where I am getting an empty response when fetching data from MongoDB Atlas using the Realm SDK. I have tried various troubleshooting steps, including deleting and recreating the database, but the problem persists. I am using Google One Tap sign-in and JWT authentication in my Android app. Additionally, I have restarted the Realm sync, but still, I receive an empty object as a response.
The below is the Diary Model, the Diary class represents the model for diary entries and uses the Realm SDK's RealmObject as a base class. It includes properties such as _id, ownerId, mood, title, description, images, and date. -
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)
}
Here is the RequestState sealed class used for handling request states:
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>()
}
The MongoRepository interface defines the contract for interacting with MongoDB. It includes the configureTheRealm() function for setting up the Realm configuration and the getAllDiaries() function for retrieving diaries as a Flow of Diaries.
Here is the code for the MongoDB object that implements the MongoRepository interface:
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>
}
The MongoDB object is responsible for configuring the Realm and retrieving diaries from MongoDB. It initializes the Realm configuration in the configureTheRealm() function using the current user and sets up an initial subscription for the user's diaries. The getAllDiaries() function retrieves diaries from Realm and maps the result to a Diaries object.
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.")
The HomeViewModel is responsible for managing the state and data of the home screen. It includes a diaries property that holds the current state of the diaries. The observeAllDiaries() function collects the getAllDiaries() flow from MongoDB and updates the diaries state accordingly.
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
}
}
}
}
I am using the following schema in mongoDb Atlas.
{
"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"
}
}
}
These are the database access rules which I have configured in Atlas
Here is the MongoDb Atlas Log of the query being made.
Upvotes: 1
Views: 288
Reputation: 11
Make these changes inside your MongoDB object, and it should work.
For this code part:
val config = SyncConfiguration.Builder(user, setOf(Diary::class))
.initialSubscriptions { sub ->
add(
query = sub.query<Diary>(query = "ownerId == $0", user.identity),
name = "User's Diaries"
)
}
Modify second line like this:
.initialSubscriptions(rerunOnOpen = true)
Upvotes: 1