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.
Related
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
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
}
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
In the firestore I created a field named PararUm, type number (it does not have Int, when I enter it manually) and I put value 1.
The problem is that the return has been PararUm(PararUm=1) and not just 1.
(99-below)
When I resolved this, I would have solved the first part of the project.
Regarding the second, I want to use kotlinx.coroutines to work within a while/loop (which queries the value of the PararUm field) in a synchronous, non-asynchronous way (as firebase requires)
Can I do something like???(999-below):
I threw this topic down, but I was not happy1.
99-below:
model
#IgnoreExtraProperties
data class PararUm(
var PararUm: Int? = 0
)
Activity
var db = FirebaseFirestore.getInstance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var pararumRef =
db.collection("smartmodel").document("xxxxxxxxxxxx")
pararumRef.get().addOnSuccessListener { documentSnapshot ->
var PararUm = documentSnapshot.toObject(PararUm::class.java)
Log.i(ContentValues.TAG, "1999 1999 1999" + PararUm)
}
}
999-below:
while (!FCMotorUmA.value) {
var snapshot = pararumRef.get().await()
var pararum = snapshot.toObject(PararUM::class.java)
if (pararum.equals(0)) {
// Do something 1
} else if (pararum.equals(1)) {
// Do something 2
}
}
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)]