Krish Vachhani
Krish Vachhani

Reputation: 11

Empty data response from MongoDB Atlas using Realm SDK

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.

Here is the Collection

Upvotes: 1

Views: 288

Answers (1)

Pawandeep Kaur
Pawandeep Kaur

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

Related Questions