I am working on an Android application in Kotlin which integrate Firebase.
Now I want to store my data (Kotlin data class) into Firebase Database.
Data Classes:
#Parcelize
data class Trip(
val fromAddress: String,
val toAddress: String,
val fromLocation: String,
val toLocation: String,
val orderUid: String
) : Parcelable
#Parcelize
data class Order(val trip: Trip, val date: Date, var status: OrderStatus, val userUid: String) : Parcelable {
var pickUpDate: Date? = null
var dropOffDate: Date? = null
var price: Double? = null
}
Fireabase Database write operation:
fun createNewOrder(
fromAddress: String,
toAddress: String,
fromLocation: Location,
toLocation: Location
) {
val fromGeoLocation = fromLocation.convertToGeoLocation()
val toGeoLocation = toLocation.convertToGeoLocation()
val userUid = sharedPreferences[CURRENT_USER_UID_KEY, ""]!!
val orderKey = databaseReference.child(DB_ORDERS_KEY).push().key
val tripKey = databaseReference.child(DB_TRIPS_KEY).push().key
val trip = orderKey?.let { createNewTrip(fromAddress, toAddress, it) }
val order = trip?.let { Order(it, Date(), OrderStatus.PENDING, userUid) }
if (trip != null && order != null && !userUid.isNullOrEmpty()) {
ordersGeoFire.setLocation(trip.fromGeoLocation, fromGeoLocation)
ordersGeoFire.setLocation(trip.toGeoLocation, toGeoLocation)
val allData = mutableMapOf<String, Any>()
allData["/$DB_TRIPS_KEY/$tripKey"] = trip?.convertToMap()
allData["/$DB_ORDERS_KEY/$orderKey"] = order?.convertToMap()
allData["/$DB_USERS_KEY/$userUid/$DB_ORDERS_KEY/$orderKey"] = true
databaseReference.updateChildren(allData)
}
}
I received this error:
com.google.firebase.database.DatabaseException: No properties to serialize found on class kotlin.Unit
Any suggestions?
The problem in your code is that the fileds inside your Trip class are not initialized. A recommended way in which you can create your model class would be:
class Trip(
val displayName: String = "",
val email: String = "",
val photoUrl: String = "",
val userId: String = ""
)
This is only what you need. And a way to create a new object of your Trip class, would be:
val trip = Trip(displayName, email, photoUrl, userId)
It was my mistake, because I was forget to add return type in my extensions convertToMap functions. Now they look like this:
fun Trip.convertToMap(): MutableMap<String, Any> {
val map = mutableMapOf<String, Any>()
map["fromAddress"] = fromAddress
map["toAddress"] = toAddress
map["fromGeoLocation"] = fromGeoLocation
map["toGeoLocation"] = toGeoLocation
map["orderUid"] = orderUid
return map
}
And also thanks to #Alex Mamo for his answer, it helps me in my investigation.
Now my code looks like this and works fine:
#Parcelize
data class Trip(
var fromAddress: String = "",
var toAddress: String = "",
var fromGeoLocation: String = "",
var toGeoLocation: String = "",
var orderUid: String = ""
) : Parcelable
#Parcelize
data class Order(
var trip: Trip? = null,
var date: Date? = null,
var status: OrderStatus? = null,
var userUid: String = ""
) : Parcelable {
var pickUpDate: Date? = null
var dropOffDate: Date? = null
var price: Double? = null
}
fun createNewOrder(
fromAddress: String,
toAddress: String,
fromLocation: Location,
toLocation: Location
): LiveData<Order> {
orderLiveData = MutableLiveData()
orderLiveData.value = null
val userUid = sharedPreferences[CURRENT_USER_UID_KEY, ""]!!
val orderKey = databaseReference.child(DB_ORDERS_KEY).push().key
val tripKey = databaseReference.child(DB_TRIPS_KEY).push().key
val trip = orderKey?.let { createNewTrip(fromAddress, toAddress, fromLocation, toLocation, it) }
val order = trip?.let { Order(it, Date(), OrderStatus.PENDING, userUid) }
if (trip != null && order != null && !userUid.isNullOrEmpty()) {
val allData = mutableMapOf<String, Any>()
allData["/$DB_TRIPS_KEY/$tripKey"] = trip.convertToMap()
allData["/$DB_ORDERS_KEY/$orderKey"] = order.convertToMap()
allData["/$DB_USERS_KEY/$userUid/$DB_ORDERS_KEY/$orderKey"] = true
databaseReference.updateChildren(allData) { databaseError, databaseReference ->
if (databaseError == null) orderLiveData.value = order
}
}
return orderLiveData
}
Hope this will be helpful
Related
This is unusuall response where the name of object is the same as the object's ID and at this point I don't really know how to parse this response
"addresses": {
"163492": {
"address_id": "163492",
//more of String variables
},
"166127": {
"address_id": "166127",
//more of String variables
},
"166202": {
"address_id": "166202",
//more of String variables
}
}
this is how my Event model looks like, I'm using room database to save this response later
#Entity
data class Event(
#PrimaryKey(autoGenerate = false)
#SerializedName("id") val id: Int,
#SerializedName("title") val title: String,
#SerializedName("description") val desc: String,
#SerializedName("note") val note: String? = null,
#SerializedName("date") val dateTs: Long,
#SerializedName("begintime") val beginTime: String,
#SerializedName("enddate") val endDate: String,
#SerializedName("endtime") val endTime: String,
#SerializedName("customerid") val customerId: String? = null,
#SerializedName("address_id") val addressId: String? = null,
#SerializedName("pin") val pin: String? = null,
#SerializedName("location") val location: String? = null,
#SerializedName("customerlocation") val customerLocation: String? = null,
#field:TypeConverters(beskidmedia.pl.scanner.room.TypeConverters::class)
#SerializedName("nodes") val nodes: List<Node>? = null,
#SerializedName("closed") val closed: Int,
#SerializedName("type") val type: Int,
#SerializedName("ticketid") val ticketId: String? = null,
#SerializedName("customername") val customerName: String? = null,
#field:TypeConverters(beskidmedia.pl.scanner.room.TypeConverters::class)
#SerializedName("contacts") val contacts: List<Contacts>? = null,
#field:TypeConverters(beskidmedia.pl.scanner.room.TypeConverters::class)
#SerializedName("addresses") val addresses: List<Address>? = null,
#Embedded
#SerializedName("assignments") val assignments: Assignments? = null,
#SerializedName("lastUpdate") val lastUpdate: Long = System.currentTimeMillis()
)
everything beside the addresses part is fine cos I tested it using response with null for addresses, I tried to do deserializer for this but it appears like it don't recognise it, this is how it looks like
class EventDeserializer : JsonDeserializer<Event> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): Event {
json?.asJsonObject!!.let { event ->
val nodes = mutableListOf<Node>()
val contacts = mutableListOf<Contacts>()
val addresses = mutableListOf<Address>()
val net = mutableListOf<Assignment>()
val tv = mutableListOf<Assignment>()
val assignments = Assignments(net, tv)
val netTemp = event.get("assignments").asJsonObject.get("assignments_net").asJsonArray
val tvTemp = event.get("assignments").asJsonObject.get("assignments_tv").asJsonArray
netTemp.forEach { assignment ->
assignment.asJsonObject.let {
net.add(
Assignment(
name = it.get("name").asString,
id = it.get("id").asInt
)
)
}
}
tvTemp.forEach { assignment ->
assignment.asJsonObject.let {
tv.add(
Assignment(
name = it.get("name").asString,
id = it.get("id").asInt
)
)
}
}
val nodesTemp = event.get("nodes").asJsonArray
nodesTemp.forEach { node ->
node.asJsonObject.let {
nodes.add(
Node(
id = it.get("id").asInt,
name = it.get("name").asString,
mac = it.get("mac").asString,
ip = it.get("ip").asString,
location = it.get("location").asString,
netName = it.get("netname").asString
)
)
}
}
val contactsTemp = event.get("contacts").asJsonArray
contactsTemp.forEach { contact ->
contact.asJsonObject.let {
contacts.add(
Contacts(
phone = it.get("phone").asString,
contact = it.get("contact").asString,
name = it.get("name").asString,
type = it.get("type").asString,
typeStr = it.get("typestr").asString
)
)
}
}
val addressesTemp = event.get("addresses").asJsonObject
addressesTemp?.keySet()?.let { names ->
names.forEach { name ->
addressesTemp.get(name).asJsonObject.let {
addresses.add(
Address(
id = it.get("address_id").asString,
name = it.get("location").asString
)
)
}
}
}
return Event(
id = event.get("id").asInt,
title = event.get("title").asString,
desc = event.get("description").asString,
note = event.get("note")?.asString,
dateTs = event.get("date").asLong,
beginTime = event.get("begintime").asString,
endDate = event.get("enddate").asString,
endTime = event.get("endtime").asString,
customerId = event.get("customerid")?.asString,
addressId = event.get("address_id")?.asString,
pin = event.get("pin")?.asString,
location = event.get("location")?.asString,
customerLocation = event.get("customerlocation")?.asString,
nodes = nodes,
closed = event.get("closed").asInt,
type = event.get("type").asInt,
ticketId = event.get("ticketid")?.asString,
customerName = event.get("customername")?.asString,
contacts = contacts,
addresses = addresses,
assignments = assignments
)
}
}
}
and this is how I'm creating gson factory
val gson = GsonBuilder().registerTypeAdapter(Event::class.java, EventDeserializer())
Retrofit
.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(
GsonConverterFactory.create(gson.create())
)
.callbackExecutor(Executors.newSingleThreadExecutor())
and the structure of response looks like this
Call<List<Event>>
but the list always have 1 element and this is artifact of old api that i can't change
Ok, so I figured this out, apparently your deserializer needs to be the exactly the same type as your response, so I added the interceptor that removed excess array that wrapped every response and now deserializer is being ussed as intended.
I'm trying to prepopulate a database, I generated the insert data but
when I run the app it gives this build error:
Caused by: org.jetbrains.org.objectweb.asm.MethodTooLargeException: Method too large:
I have a funtion like this:
RoomDatabase.Callback(){
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
val pDao = database.get().pDao()
val pLangDao = database.get().pLangDao()
applicationScope.launch {
insertPWithPLangEnglish_1_10(pDao, pLangDao)
}
}
}
private suspend fun insertPWithPLangEnglish_1_10(pDao: PDao, pLangDao: PLangDao){
var insId = pDao.insert(P(pcId = 1))
pLangDao.insert(PLang(pItemId = insId.toInt(), title = "herbert", locale = "en_US", langCode = "en"))
insId = pDao.insert(P(pcId = 1))
pLangDao.insert(PLang(pItemId = insId.toInt(), title = "others", locale = "en_US", langCode = "en"))
... and so on about 3000 more lines
}
Any idea how to solve this?
P class is the following:
#Entity(tableName = "p")
#Parcelize
data class P (
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "pid")
val pId: Int = 0,
#ColumnInfo(name = "pc_id")
val pcId: Int,
val created: Long = System.currentTimeMillis()
) : Parcelable {
}
PLang class is the following:
#Entity(tableName = "p_lang")
#Parcelize
data class PLang (
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "p_lang_id")
val pLangId: Int = 0,
#ColumnInfo(name = "p_item_id")
val pItemId: Int,
val locale: String = "",
#ColumnInfo(name = "lang_code")
val langCode: String,
val title: String,
val description: String = ""
) : Parcelable {
}
Now I'm trying another way with help of Workers, and seed database from json files.
So clearly, the Kotlin compiler is complaining because your method is too long and you should optimize it.
In order to do that you could create a global list containing all your PLang objects:
val pLangList = listOf(
PLang(title = "herbert", locale = "en_US", langCode = "en"),
PLang(title = "others", locale = "en_US", langCode = "en"),
...
)
Note that I didn't set the pItemId property, in fact, you should also change your PLang class, assigning a default value to it for convenience:
#Entity(tableName = "p_lang")
#Parcelize
data class PLang (
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "p_lang_id")
val pLangId: Int = 0,
#ColumnInfo(name = "p_item_id")
val pItemId: Int = -1, // <--- Added default value here
val locale: String = "",
#ColumnInfo(name = "lang_code")
val langCode: String,
val title: String,
val description: String = ""
) : Parcelable {
}
Now you can loop through your list and add every item with just 3 lines of code instead of ~3000:
private suspend fun insertPWithPLangEnglish_1_10(pDao: PDao, pLangDao: PLangDao)
{
for (pLang in pLangList)
{
val insId = pDao.insert(P(pcId = 1))
pLangDao.insert(PLang(pItemId = insId.toInt(), title = pLang.title, locale = pLang.locale, langCode = pLang.langCode))
}
}
Can't see how to do this and getting rather confused!
I am saving 'site' objects to firestore, but I want to add a list of users associated to each site.
I have added a Map of users to my JSON object as below:
#IgnoreExtraProperties
data class SiteObject(
var siteReference: String,
var siteAddress: String,
var sitePhoneNumber: String,
var siteEmail: String,
var invoiceAddress: String,
var invoicePhoneNumber: String,
var invoiceEmail: String,
var website: String,
var companyNumber: String,
var vatNumber: String,
var recentProjectsText: String,
//not set up yet:
var sitePriority: Boolean,
var siteRating: Int,
var plusCode: String,
var users: Map<String, Boolean>?, // This is the map I have added
#ServerTimestamp
var dateCreatedTimestamp: Date?,
#ServerTimestamp
var dateEditedTimestamp: Date?,
#Exclude
var siteID: String?
) : Serializable {
private constructor() : this(
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
false,
1,
"",
null,
null,
null,
null
)
override fun toString(): String {
return "$siteReference"
}
}
And in my respository I am trying to add the current user to this list of users as below:
// save sites to firebase
fun saveSite(site: SiteObject) {
site.users?.plus(Pair(firebaseUser?.uid.toString(), true)) // This is where I expected the user Id to be added to Map of users..
val documentReference = firestore.collection("sites").document().set(site)
.addOnCompleteListener {
if(it.isSuccessful){
Log.d(TAG, "${site.toString()} saved")
lastOperationText.value = "New site, ${site.siteReference}, saved!"
} else {
Log.d(TAG, "${site.toString()} saved")
lastOperationText.value = "Save new site failed"
}
}
}
However, I still seeing null for users in the Firestore console.
Your code never gives an initial value to users. It starts off null. Since it doesn't get assigned a value, this code will not make a change to it, because it's first checking to see if users is null using the ?. operator:
site.users?.plus(Pair(firebaseUser?.uid.toString(), true))
You will need to assign it an initial value before trying to modify it. It should probably never be null and just start empty.
var users = HashMap<String, Boolean>()
For completeness, below is my updated data class. This initialises the values when it is created and also includes #Exclude #set:Exclude #get:Exclude on siteID to prevent this being saved to Firestore (used to store generated id when read from Firestore):
#IgnoreExtraProperties
data class SiteObject(
var siteReference: String = "",
var siteAddress: String = "",
var sitePhoneNumber: String = "",
var siteEmail: String = "",
var invoiceAddress: String = "",
var invoicePhoneNumber: String = "",
var invoiceEmail: String = "",
var website: String = "",
var companyNumber: String = "",
var vatNumber: String = "",
var recentProjectsText: String = "",
var sitePriority: Boolean = false,
var siteRating: Int = 1,
var plusCode: String = "",
var users: HashMap<String, Boolean> = hashMapOf(),
#ServerTimestamp
var dateCreatedTimestamp: Date? = null,
#ServerTimestamp
var dateEditedTimestamp: Date? = null,
#Exclude #set:Exclude #get:Exclude
var siteID: String = ""
) : Serializable {
override fun toString(): String {
return "$siteReference, $siteAddress, $siteRating, $siteID"
}
fun siteDetailsText(): String {
var siteDetailsText = siteAddress
if (sitePhoneNumber.isNotEmpty()) siteDetailsText.plus("\n\n$sitePhoneNumber")
if (website.isNotEmpty()) siteDetailsText.plus("\n\n$website")
return siteDetailsText
}
fun invoiceDetailsText(): String {
var invoiceDetailsText = invoiceAddress
if (invoicePhoneNumber.isNotEmpty()) invoiceDetailsText.plus("\n\n$invoicePhoneNumber")
if (companyNumber.isNotEmpty()) invoiceDetailsText.plus("\n\n$companyNumber")
if (vatNumber.isNotEmpty()) invoiceDetailsText.plus("\n\n$vatNumber")
return invoiceDetailsText
}
}
This is my string:
{"array":[{"message":"test1","name":"test2","creation":"test3"},{"message":"test1","name":"test2","creation":"test3"}]}
And I want it get that array into a list of object in Kotlin app for Android.
I tried to do it using two examples from this site... So here is my code (res = that string):
val gson = Gson()
val obj = gson.fromJson(res, JsonObject::class.java)
val arr = obj.getAsJsonArray("array")
println(arr.toString())
val list1 : List<JThread> = gson.fromJson(arr, object : TypeToken<List<JThread>>() {}.type)
val list2 = gson.fromJson(arr, Array<JThread>::class.java).asList()
for (x in list1){
println(x.message)
}
for (x in list2){
println(x.message)
}
However I'm only getting null in x.message. I don't know what can go wrong.
I also tried changing arr to arr.toString() everywhere and that didn't work either.
Also JThread is:
object JThread {
var message: String? = null
var name: String? = null
var creation: String? = null }
This can be done without GSON or any other third party library:
#Throws(JSONException::class)
fun JSONObject.toMap(): Map<String, Any> {
val map = mutableMapOf<String, Any>()
val keysItr: Iterator<String> = this.keys()
while (keysItr.hasNext()) {
val key = keysItr.next()
var value: Any = this.get(key)
when (value) {
is JSONArray -> value = value.toList()
is JSONObject -> value = value.toMap()
}
map[key] = value
}
return map
}
#Throws(JSONException::class)
fun JSONArray.toList(): List<Any> {
val list = mutableListOf<Any>()
for (i in 0 until this.length()) {
var value: Any = this[i]
when (value) {
is JSONArray -> value = value.toList()
is JSONObject -> value = value.toMap()
}
list.add(value)
}
return list
}
Usage to convert JSONArray to List:
val jsonArray = JSONArray(jsonArrStr)
val list = jsonArray.toList()
Usage to convert JSONObject to Map:
val jsonObject = JSONObject(jsonObjStr)
val map = jsonObject.toMap()
More info is here
Use this code:
import com.google.gson.annotations.SerializedName
import com.google.gson.Gson
data class Array(
#SerializedName("message")
var message: String,
#SerializedName("name")
var name: String,
#SerializedName("creation")
var creation: String
)
data class Example(
#SerializedName("array")
var array: List<Array>? = null
)
private fun fromJson(json:String):Example{
return Gson().fromJson<Example>(json, Example::class.java)
}
PS: I made it with this site:http://www.jsonschema2pojo.org/
DBFlow Version: 4.0.4
Hi, I'm strugling with List Type converter with dbflow Android ORM and Kotlin. I have a data class defined like this:
#Table(database = StopsDb::class)
data class FavouriteStop(
#PrimaryKey #Column var id: String = "",
#Index #Column var name: String = "",
#Column(typeConverter = StringListConverter::class) var directions: List<String> = listOf(),
#Column(typeConverter = StringListConverter::class) var selectedDirections: List<String> = listOf()
) : BaseRXModel()
and as I don't want to create a separate table only to store Strings I created a List Type converter like this:
class StringListConverter : TypeConverter<String, List<String>>() {
val separator = ","
override fun getDBValue(model: List<String>?): String {
if(model==null || model.isEmpty())
return ""
else
return model.joinToString (separator = separator){ it }
}
override fun getModelValue(data: String?): List<String> {
return data?.split(separator) ?: listOf()
}
}
however following error is thrown during build phase:
Error:error: *==========* :The specified custom TypeConverter's Model Value java.util.List<? extends java.lang.String> from com.kapuscinski.departures.persistence.db.StringListConverter must match the type of the column java.util.List<java.lang.String>.*==========*
Am I missing something here, and how to fix this? Thanks in advance for help!
Change everything from "List" to "MutableList"
#Table(database = StopsDb::class)
data class FavouriteStop(
#PrimaryKey #Column var id: String = "",
#Index #Column var name: String = "",
#Column(typeConverter = StringListConverter::class) var directions: MutableList<String> = mutableListOf(),
#Column(typeConverter = StringListConverter::class) var selectedDirections: MutableList<String> = mutableListOf()
) : BaseRXModel()
class StringListConverter : TypeConverter<String, MutableList<String>>() {
val separator = ","
override fun getDBValue(model: MutableList<String>?): String =
if (model == null || model.isEmpty())
""
else
model.joinToString(separator = separator) { it }
override fun getModelValue(data: String?): MutableList<String> {
return data?.split(separator)?.toMutableList() ?: mutableListOf()
}
}