Kotlin firebase read object from db - firebase

I have a problem reading an object from the db without reading the single fields ad recostructing the object manually.
A simple example of the code.
This is my model:
data class Ingredient(val name: String, val quantity: Int){
}
// yes, just one ingredient for the moment =)
data class Recipe(val name: String, val ingredient: Ingredient){
}
Then I save an instance on db:
val i1 = Ingredient("ing1", 300)
val r = Recipe("rec1", i1)
val db = FirebaseFirestore.getInstance()
db.collection("recipies").document(r.name).set(r.ingredient)
Finally I try to read the ingredient:
db.collection("recipies").document(r.name).addSnapshotListener {
documentSnapshot, _ ->
if(documentSnapshot != null){
val ingredient: Ingredient? = documentSnapshot.get("ingredient", Ingredient::class.java)
}
}
The problem is that ingredient is null.
How can I fix the problem?
I had a model with a list, but I found out that even this simpler example did not work.
This is the db structure:

db.collection("recipies").document(r.name).addSnapshotListener {
val ingredient = documentSnapshot?.toObject(Ingredient::class.java) //<-- Use .toObject instead
}

Related

Convert Firebase DocumentSnapshot data to a custom Scala (or Java) class

I'm using Firebase for a private Scala project and I'm struggling to understand how I can manage Firebase responses if I want to avoid using HashMap.
This is the information that I'm trying to manage:
These are the two Scala classes that I wrote with the idea to use them along with the toObject method:
class Doc() {
#BeanProperty val map: util.HashMap[String, Value] = new util.HashMap[String, Value]()
}
class Value() {
#BeanProperty val displayName: String = ""
#BeanProperty val email: String = ""
// Extra fields that I need to initialize in the Scala code
#BeanProperty val totalLogins: Int = 0
#BeanProperty val todoMap: util.HashMap[String, String] = new util.HashMap[String, String]()
#BeanProperty val todoList: util.ArrayList[String] = new util.ArrayList[String]()
#BeanProperty val totalChanges: Int = 0
#BeanProperty val totalErrors: Int = 0
}
And this is snapshot listener implementation that I wrote:
docFirebase.addSnapshotListener(new EventListener[DocumentSnapshot]() {
override def onEvent(snapshot: DocumentSnapshot, e: FirestoreException): Unit = {
if (e != null) {
println("[OnSnapshot] Listen failed: " + e)
return
}
if (snapshot != null && snapshot.exists) {
val doc = snapshot.toObject(classOf[Doc])
// Here below I'll write the complex logic I need ...
} else {
println("[OnSnapshot] Current data: null")
}
}
})
Using that code I'm always getting an empty HashMap into the doc variable. Can someone helps me understand what I misunderstood about reading data from Firebase ? Thanks in advance.
All of your properties in the document are nested under an object called "abc". That fact is not reflected in your code - you need to call out abc by name to get all the nested fields from it. You probably don't want that nesting at all. Just put all the fields (displayName, email, etc) as top-level fields in the document.

When using Kotlin Exposed, how do I handle missing values for numbers in an sqlite database?

I have an sqlite data base that entries similar this example:
0|some_text0|123.456||some_other_text0
1|some_text1||456.789|some_other_text1
So as you can see the 3rd and 4th column are numbers, but in some records the data in the column is empty.
I defined the interface using something similar to this example.
object Foos : IntIdTable("foos") {
val sequelId = integer("id").primaryKey()
val text0 = text("text0")
val num0 = float("num0").nullable()
val num1 = float("num1").nullable()
val text1 = text("text1")
}
class Foo(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Foo>(Foos)
var sequelId by Foos.sequelId
var text0 by Foos.text0
var num0 by Foos.num0
var num1 by Foos.num1
var text1 by Foos.text
}
If I query a record that contains a missing value and try to access the field I throw an exception such as java.lang.String cannot be cast to java.math.Float?
for example:
val x = queryResult0.num1 //fails
I can get passed this via:
val x = try { queryResult0.num1 } catch(e: Exception) { null } // works I get a value or null
My current "hacky" solution is to add a second set of getters for the fields such this:
class Foo(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Foo>(Foos)
var sequelId by PinTimings.sequelId
var text0 by Foos.text0
var num0 by Foos.num0
var num1 by Foos.num1
var text1 by Foos.text
val num0x : Float?
get() = try { num0 } catch(e: Exception) { null }
val num1x : Float?
get() = try { num1 } catch(e: Exception) { null }
}
Then queryResut0.num1x provides what I need.
What's the right way to handle this kind of situation?
FYI: I am using the following setup
Kotlin: 1.2.61
Exposed: org.jetbrains.exposed:exposed:0.17.7
JDBC: org.xerial:sqlite-jdbc:3.32.3.2

Migration is required due to Property id has been made required

it works fine if i change lateinit var id: String in the Payment.kt and CartPayment.kt to var id: String? = "", but the problem is i want the id to be required, how can i achieve that ?
The Error:
java.lang.RuntimeException: Unable to create application: io.realm.exceptions.RealmMigrationNeededException: Migration is required due to the following errors:
- Property 'CartPayment.id' has been made required.
- Property 'Payment.id' has been made required.
Model :
open class Payment() : RealmObject() {
#PrimaryKey
lateinit var id: String
var typeValue: Int = 0
var statusValue: Int = 0
var value: Double = 0.0
var referenceNumber: String? = null
Note: Payment and CartPayment models are identical except for the class name
Migration.kt
class Migration : RealmMigration {
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
var oldVersion = oldVersion
val schema = realm.schema
if (oldVersion == 0L) {
schema.create("Payment")
.addField("id", String::class.java, FieldAttribute.PRIMARY_KEY)
.addField("typeValue", Int::class.java)
.addField("statusValue", Int::class.java)
.addField("value", Double::class.java)
.addField("referenceNumber", String::class.java)
schema.get("Order")!!
.addRealmListField("payments", schema.get("Payment")!!)
oldVersion++
}
if (oldVersion == 1L) {
schema.create("CartPayment")
.addField("id", String::class.java, FieldAttribute.PRIMARY_KEY)
.addField("typeValue", Int::class.java)
.addField("statusValue", Int::class.java)
.addField("value", Double::class.java)
.addField("referenceNumber", String::class.java)
schema.get("Order")!!
.addField("cashPaymentAmount", Float::class.java)
.addField("change", Float::class.java)
oldVersion++
}
}
}
App.kt
class App: Application() {
override fun onCreate() {
super.onCreate()
Realm.init(this)
val realmConfig = RealmConfiguration.Builder()
.schemaVersion(2)
.migration(Migration())
.build()
Realm.getInstance(realmConfig)
Realm.setDefaultConfiguration(realmConfig)
Fresco.initialize(this)
}
}
.addField("id", String::class.java, FieldAttribute.PRIMARY_KEY, FieldAttribute.REQUIRED)
did the trick.
if you declare the variable to be lateinit, make sure to add FieldAttribute.REQUIRED.
Basically you are adding a new field "id" which is primary key (hence required key).
If you do not specify any value while initialisation (lateinit), how will realm migrate all the earlier records, which doesn't have an id, but is required after migration ? Hence the error.
Below solutions might work
Either pre-populate the id's (without using lateinit)
Transform your earlier records to have id's if they don't have
Check the official examples here
For me, it happened after I have done the Migration.
I have made a non-nullable object in Kotlin and on migration I was creating a nullable wrapper type like Double, Int, etc.
Just use
Double::class.java
instead of
Double::class.javaObjectType

Firebase Database multiple strings in one child

im working with firebase for a couple months and so far i had no issues until now. im using kotlin and my problem is simple but i cant find a way out. i storage strings in database: path"/user/friends" , but i cant change the variable name and the strings were overwriting itselves. My solution was use "push()" until the "setValue()", but with this i have the following firebase structure:
"users": {
"yvrYpjMwVSPBvMAvDGo26hPlWWQ2": {
"email": "vinibarros.sp#gmail.com",
"friends": {
"-LfD9z6ke7FXFjxUb4td": {
"email": "teste#gmail.com"
},
"-LfDA-NaAYAMoWiPhXy4": {
"email": "teste2#gmail.com"
}
},
"primeiroLogin": true,
"stars": 0,
"tutorialComplete": false,
"uid": "yvrYpjMwVSPBvMAvDGo26hPlWWQ2",
"username": "Vinicius Barros"
}
}
basically hashmaps inside a hashmap ps:lol mindblow
how do i get the pushcode.value into an arraylist?
i have a class user that have the friend variable, until now this variable was typed as hashMap. and the call to get the email was "user.friends.value"
this worked because was one friend....
com.google.firebase.database.DatabaseException: Failed to convert value of type java.util.HashMap to String
When I added more than one user I got this error that references my class user, where have a "hashMap". But the firebase gives me another hashmap instead of string. I was thinking about: hashMap<hashMap<String,String>, null>.
i 've tried this:
class friends:
#Parcelize
class Friend(val hashMap: HashMap < String, String > ? ): Parcelable {
constructor(): this(null)
}
The activity where i show the friends:
val ref2 = FirebaseDatabase.getInstance().getReference("/users/" + cUid + "/friends/")
ref2.addListenerForSingleValueEvent(object: ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(p0: DataSnapshot) {
p0.children.forEach {
val friend = it.getValue(Friend::class.java)
Log.d("teste", it.toString())
if (friend != null) {
Log.d("teste", friend.hashMap ? .values.toString())
//friends.add(friend.hashMap?.values.toString())
}
}
}
})
this keeps returning null....
With your Friend class, the Firebase client looks for a JSON for each friend with this structure:
"-LfD9z6ke7FXFjxUb4td": {
"hashMap": {
...
}
},
This is because you're defining your Friend class with a property hashMap like this: class Friend(val hashMap: HashMap < String, String > ? ).
To be able to read your current structure, you'll need to define a data class like this:
data class Friend (
val email: String? = null
}
Now the email in the Friend class matched the email property in the JSON. And since email has a default value, the Friend class will have a default no-argument constructor, which Firebase relies on.
Solved!
After get into /friends
i did this:
var friends : ArrayList<String> = arrayListOf()
the onDataChanged where i get the string friend
override fun onDataChange(p1: DataSnapshot) {
p1.children.forEach { it1 ->
val friend = it1.value as String
friends.add(friend)
}
}

How to transform a Custom Object List by excluding particular property in Kotlin?

How to transform a List into a new List by excluding a property in T.
For instance if User data class has 10 properties, I need to transform List into a new List without one particular property in User . New List like List
data class User(val name: String, val age: Int)
var userList = mutableListOf<User>()
var nameList= userList.map { it.name }
If a List to be created without property 'age'. Like
var withoutAgeList
In your first example:
var userList = mutableListOf<User>()
var nameList= userList.map { it.name }
The question "What's the type of nameList?" has a simple answer: List<String>. So let me ask you a similar question: What's the type of withoutAgeList? The answer to that question informs the answer to your question.
Perhaps a user without the age property is a separate AgelessUser class, meaning withoutAgeList is of type List<AgelessUser>. In that case, I suggest either a constructor or a factory function that builds AgelessUser from User, and you want one of these:
val withoutAgeList = userList.map { AgelessUser(it) } // constructor
val withoutAgeList = userList.map { agelessUserOf(it) } // factory
Alternatively, maybe the age property in User is nullable and immutable, and you want to represent users without an age as a regular User where age=null. In this case, you could copy the Users and override the age field
// TODO: pass all the other fields too
val withoutAgeList = userList.map { User(it.name, null) }
Assuming Users is a data class, we can avoid explicitly naming all fields by making use of copy():
val withoutAgeList = userList.map { it.copy(age = null) }
Maybe the age property is nullable and mutable — and you actually want to change the users in place instead of copying them. This is somewhat risky and I don't advocate doing it this way unless you really know what you're doing though.
userList.forEach { it.age = null }
// They're actually the same list!
val withoutAgeList = userList
In such a simple case you can map a list of Users into a list of strings:
val names: List<String> = userList.map(User::name)
Or you can declare a DTO and map into the latter:
class UserWithoutAge(val name: String)
val usersWithoutAge: List<UserWithoutAge> = userList.map { UserWithoutAge(it.name) }
P.S. you don't have to write an explicit type
You can use the Object Oriented approach:
data class User(val name: String, val age: Int)
data class UserNoAge(var name: String) {
constructor(user: User) : this(user.name)
}
var userList = listOf(User("John", 25), User("Jane", 30))
var userNoAge: List<UserNoAge> = mutableListOf<UserNoAge>()
userNoAge = userList.map{ UserNoAge(it) }
println(userNoAge) // [UserNoAge(name=John), UserNoAge(name=Jane)]

Resources