Local changes not updating Atlas in Flexible Sync'd realm - android

I have a collection containing Group objects with the following schema:
class Group : BaseEntity(), RealmObject {
class UserInfo : EmbeddedRealmObject {
var userId: Id? = null
var username: String? = null
var userBalance: Double = 0.0
}
#PrimaryKey
override var _id: Id = createId()
var groupName: String? = null
var users: RealmList<Id> = realmListOf()
var userInfo: RealmList<UserInfo> = realmListOf()
}
Group.users and Group.usersInfo is mimicking a dictionary with userId as key and UserInfo containing nickname, balance, etc. as value. Apparently the Kotlin SDK doesn't support RealmDictionary nor querying on embedded objects according to Kotlin RQL docs, that's why the schema is a little messy. My Sync subscription for the Groups collection is:
val config =
SyncConfiguration.Builder(realmUser, setOf(Group::class, Group.UserInfo::class))
.initialSubscriptions(rerunOnOpen = true) { realm ->
add(realm.query<Group>("$0 IN users", userId))
}
.name("groupRealm")
.build()
val realm: Realm by lazy { Realm.open(config) }
where the subscription query
add(realm.query<Group>("$0 IN users", userId))
is attempting to sync all Group objects that a userId belongs to, basically, through the Group.users and Group.userInfo fields I described earlier.
All the read and write operations into the local realm are fine, but when I go into the collections in Atlas, I don't see the updates in the Group collection. Also, when I add .waitForInitialRemoteData() to the config, my code times out. I also get an OtherSessionError: operation canceled (ProtocolErrorCode=201) in the App Services logs, and an empty query when it should show the subscription query mentioned above. Any help on this issue is greatly appreciated!

Related

Saving complex Class data in Room - "Cannot figure out how to save this field into database. You can consider adding a type converter for it"

I started to learn Room and I'm facing an issue:
Given two classes, one is a Car, and the other one is an Engine iside a Car.
#Entity
class Car{
#PrimaryKey
var id = 0
var name: String? = null
var engine: Engine? = null
}
...
#Entity
class Engine{
#PrimaryKey
var id = 0
var manufacturer: String? = null
}
I also have these classes initalized to tables in my AppDatabase class.
#Database(entities = [Car::class, Engine::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
//...
}
The problem is whenever I simply want to run the project I get the following error message which points to the Car's engine field:
Cannot figure out how to save this field into database. You can consider adding a type converter for it.
Is there no simple way for this? I'm looking for something which saves my data with the least amount of code, like Firestore which do all the work with simple annotations.
Thanks in advance.
AS a car would only have a single engine and that you have a table for the engine as well as a table for the car. Then you have a 1 to many relationship. That is a car can have an engine but the same engine can be used by many cars.
So instead of trying to embed the engine within the car you make a relationship, the car (the child) referencing the engine (the parent).
This is as simple as changing the Car to be:-
#Entity
class Car{
#PrimaryKey
var id = 0
var name: String? = null
var engine: Int? = null
}
An alternative, that would not need the relationship nor a TypeConverter would be to not have the Engine as a table but to use the #Embedded annotation prior to the engine. e.g.
#Entity
class Car{
#PrimaryKey
var id = 0
var name: String? = null
#Embedded
var engine: Engine? = null
}
...
class Engine{
#PrimaryKey
#ColumnInfo(name = "engineId")
var id = 0
var manufacturer: String? = null
}
the name of the column used to store the Engine's id changed as otherwise there would be 2 columns with the same name.
Note that with this way there is no need for the #Entity annotation as you are storing the Engine values within the Car.
This is not considered good practice as if the same engine is used by many cars then you are duplicating data and thus that it is not normalised.
The third and least desirable way from a database perspective is to store a representation of the engine object in a single column. That is to convert the object into a singular storable representation. Typically a JSON string. Thus you need code (a function) to convert from the object to the single value (JSON string) and (another function) to convert from the JSON String to the Object.
With this method not only are you not normalising the data but additionally you end up storing the bloat required to enable the object to be represented. That bloat, from a database, perspective, obfuscating the actual useful stored data to some extent.
In addition there is not a single set/standard library providing the functionality of converting objects to/from JSON, so you have to select a flavour and then include that library in the project.
Here is a class that contains Type Converters that could be used (see comment re library):-
class CarAndEngineTypeConverters{
/* Using Library as per dependency implementation 'com.google.code.gson:gson:2.10.1' */
#TypeConverter
fun convertEngineToJSONString(engine: Engine): String = Gson().toJson(engine)
#TypeConverter
fun convertJSONStringToEngine(jsonString: String): Engine = Gson().fromJson(jsonString,Engine::class.java)
}
This would suit your original classes.
Room needs to be told to use these classes (it works out when) via a #TypeConverters annotation (note the plural and not singular) this it immediately before or after the #Database annotation has the highest level of scope. The annotation itself could be #TypeConverters(value = [CarAndEngineTypeConverters::class])
To demonstrate all three together consider this over the top Car class:-
#Entity
class Car{
#PrimaryKey
var id = 0
var name: String? = null
var engine: Int? = null
#Embedded
var alternativeEngine: Engine? = null
var jsonConvertedEngine: Engine? = null
}
Over the top as the engine is stored 3 times (could be different engines)
The *Engine class
#Entity
class Engine{
#PrimaryKey
#ColumnInfo(name = "engineId")
var id = 0
var manufacturer: String? = null
}
The Type Converters as above.
With the above in place and using within an activity (noting that for brevity/convenience .allowMainThreadQueries has been used):-
db = TheDatabase.getInstance(this)
carAndEngineDAO = db.getCarAndEngineDAO()
var engine1 = Engine()
engine1.manufacturer = "Ford"
engine1.id = carAndEngineDAO.insert(engine1).toInt()
var car1 = Car()
car1.name = "Escort"
car1.engine = engine1.id /* id of the engine */
car1.alternativeEngine = engine1
car1.jsonConvertedEngine = engine1
carAndEngineDAO.insert(car1)
Using Android Studios App inspection the view the database then
The Columns id and name and obviously as expected
The engine column contains the value 0, this is the id of the respective engine in the engine table (maximum 8 bytes to store the id)
The JsonConvertedEngine column stores the JSON representation of the Engine (31 bytes)
The engineId column and manufacturer column stores the respective values (12 bytes).
The Engine Table (only needed for the relationship) is :-
You should use TypeConverters:
At first add this dependency to your project to convert Engine to Json and vice versa
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
Now you should create an Object class that convert Engine to Json. This class make Engine understandable for Room :
object CommonTypeConverters {
#TypeConverter
#JvmStatic
fun stringToEngine(value: String): Engine = fromJson(value)
#TypeConverter
#JvmStatic
fun engineToString(items: Engine?): String = toJson(items)
inline fun <reified T> toJson(value: T): String {
return if (value == null) "" else Gson().toJson(value)
}
inline fun <reified T> fromJson(value: String): T {
return Gson().fromJson(value, object : TypeToken<T>() {}.type)
}
In the end Engine is not a entity and you should add #Typeconverter annotation to your database class :
#Database(entities = [Car::class], version = 1)
#TypeConverters(CommonTypeConverters::class)
abstract class AppDatabase : RoomDatabase() {
//...
}

I cannot seem to connect to Firestore database to save user's data

I'm currently working on an Android project and I've been stuck with this problem for a few hours now.
I'm trying to connect to my Firestore database. The idea is to store documents with additional info from the users. It's created on-register and then sent to the database.
Here's the code:
auth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success
Log.d("RegistroFirebase", "createUserWithEmail:success")
val user = auth.currentUser
// Create user's database document
writeNewUser(
user!!.uid, user!!.email, binding.signUpName.text.toString(),
binding.signUpSurname.text.toString(), "623623623")
Log.d("Crear documento usuario", "Success?")
reload("main")`
And the function:
private fun writeNewUser(userId: String, email: String?, name: String, surname: String, phone:String) {
val user = User(email, name, surname, phone)
db.child("users").child(userId).setValue(user)
}
Also I have a class for users:
data class User(val email:String? = null, val name:String? = null,
val surname:String? = null, val phone:String? = null) {}
As for the error, I get none. It just works but it doesn't add anything new to my Firestore 'user' collection.
Any help would be appreciated. Thank you in advance
You say:
I cannot seem to connect to Firestore database to save user's data
And it makes sense, since when using the following lines of code:
val user = User(email, name, surname, phone)
db.child("users").child(userId).setValue(user)
You are trying to write data to the Realtime Database and not to Cloud Firestore. While both, databases are a part of Firebase services, they are different databases with different mechanisms. To be able to write data to Firestore, please use the following line of code:
FirebaseFirestore db = FirebaseFirestore.getInstance();
CollectionReference usersRef = db.collection("users");
usersRef.document(userId).set(user)
You can also attach a listener to the complete operation, to see if something goes wrong.
Similar to user creation, the setValue function can also be listened to with addOnCompleteListener, addOnSuccessListener, addOnFailureListener.
Ref: Add a Completion Callback.
You need to diagnose the result through these.

Verify if POJO data class was mapped correctly

I am using a POJO data class with GSON to parse the data which is being called from the Firestore database.
For example, I have a POJO class with few non-nullable, and nullable values like userID
data class Users(id:String="", userID:String="" ...)
I am then using GSON to parse the JSON data to object for that class
val gson = Gson()
val jsonObjects = gson.toJson(querySnapshot.data)
val parseData = gson.fromJson(jsonObjects,Users::class.java)
My question is if someone uploads data in the database and forgets to add the userID (i.e. it is null), is there a way I can check if the User data class is valid when being parsed?
I am using a check like if(userID == ""){return false} . But as the number of non-nullable fields grows it gets tedious and there must be a better way to check this.
My question is if someone uploads data in the database and forgets to add the userID (i.e. it is null), is there a way I can check if the User data class is valid when being parsed?
If you don't want to have null values at all, why would you then let the user the possibility to provide incomplete data? The simplest solution would be to restrict the data that is added to the database. How? Simply by creating some constraints. For example, your users cannot get access to a feature in your app if they do not fill in all the required fields. That's an operation that is widely used.
If you already have null values, then checking against nullity is a solution you can go ahead with. In Kotlin, null safety can be handled in many ways, either by checking for null in conditions, using safe calls, elvis operator or even using the !! operator.
Maybe the extension method of Kotlin is an accepted approach.
Let me show a demo, I assume the class User looks like this:
data class User(
val id: String,
val userId: String? // nullable
)
Create a extension method User.isValid() to verify the fields you want.
fun User.isValid() = when {
userId.isNullOrEmpty() -> false
else -> true
}
The method isNullOrEmpty() is in stdlib of Kotlin. I test the code, it works fine.
val user1 = User("id 001", null)
val user2 = User("id 002", "userId 001")
println(user1.isValid()) //==> print false
println(user2.isValid()) //==> print true
Now, back to your worry point:
...But as the number of non nullable fields grow it gets tedious
I changed the class User
data class User(
val id: String,
val userId: String?,
val email: String?,
val name: String?
)
it means that when the fields of userId, email, name, any of them is null, the User is invalid.
Just add conditions in extention method, like this:
fun User.isValid() = when {
userId.isNullOrEmpty()
|| email.isNullOrEmpty()
|| name.isNullOrEmpty() -> false
else -> true
}
We just need to maintain the method of isValid.
conclusion
Kotlin Extension Method can be used in your case.
It's better don't use id="", also can create an extension method for this empty string if need.
fun String.Companion.empty() = ""
data class User(
val id: String,
val userId: String? = String.empty()
...
)
All the extension methods can be placed in a class, like UserExt.kt for easy maintaining.

Android Kotlin Dynamic column and row listing without creating a model each table?

We have many report View tables on the MSSQL database and new report View tables are constantly being added. I created the API endpoint to pull the data from these tables with their names, such as: /report/:reportname
When I enter the table name in the report section of the link, it lists all the data in the table as JSON.
The numbers of columns in tables are not equal.rows and columns dimanic. How do I list data without creating individual models for each table?
Is there a library that lists data without writing a Model ?
class ReportModel {
#SerializedName("data")
var data: List<DataEntity>? = null
#SerializedName("report")
var report: Boolean = false
class DataEntity {
#SerializedName("FILTRE")
var FILTRE: String? = null
#SerializedName("FILTRETIPI")
var FILTRETIPI: String? = null
#SerializedName("ACIKLAMA")
var ACIKLAMA: String? = null
#SerializedName("VIEW_ADI")
var VIEW_ADI: String? = null
#SerializedName("RAPOR_ADI")
var RAPOR_ADI: String? = null
#SerializedName("RAPOR_ID")
var RAPOR_ID: String? = null
}
}
I want models to be generated automatically according to the data from the endpoint API
how can I solve this problem ?

Exactly ONE TO ONE Relation in Room

I believe it have been asked several times but no working solution.
Room has #Relation annotation which is used for one to many relationships. That's basically if you have User and Pet models, as User can have several Pets, #Relation annotation works perfectly as return type of Pets are list (or set).
class UserAndAllPets : User() {
#Relation(parentColumn = "id", entityColumn = "userId")
var pets: List<Pet> = arrayListOf()
}
The problem is what if in my case User and Pet is one to one related. As in every user can have one pet. That means there is no point of using #Relation as it only supports list or set return types. And it's totally inefficient to use as a list, even if I use it. So I am looking for a exact one to one relation where I can get a result of
class UserAndPet {
var user: User? = null
var pet: Pet? = null
}
I have tried tried several was as well as this method (which has lots of upvotes, but it doesn't work).
Supposedly,
class UserAndPet {
#Embedded
var user: User? = null
#Embedded
var pet: Pet? = null
}
Should work, but I am getting
Not sure how to convert a Cursor to this method's return type (UserAndPet)
There is no conflict as I already use prefix for #Embedded fields.
And please, can you not redirect my to any other post on stack overflow, as I tried all but no luck.
Thanks
This feature has been added as of 2.2.0-alpha01 of room.
Ref - Room release 2.2.0-alpha01
When we considering 1 to 1 relationship below approach is also possible and for more please follow the link.
#Entity(tableName = "user")
class User(
val id: Int
// ...
) {
#Ignore /* Ignores the marked element from Room's processing logic. */
var pet: Pet? = null
}
#Entity(tableName = "pet")
class Pet(
val id: Int,
val userId: Int
)
/* Single task used for 1 to 1 read */
class UserWithPetReadTask : RxTask.CallableWithArgs() {
private var result = UserWithPetTaskResult()
override fun call(params: Array<out Any>?): Any {
App.mApp?.getDBLocked { appDb ->
/* Read user details from repo */
val user: User? = appDb?.getUserDao()?.getUserById("[userId]")
user.let {
/* Read pet associated and assign it to user*/
it?.pet = appDb?.getPetDao().getPetAssociated("[userId] or [user?.id]")
result.user = user
result.isSuccess = true
}
}
return result
}
}
class UserWithPetTaskResult {
var isSuccess = false
var user: User? = null
}

Categories

Resources