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.
Related
I have a dataclass:
data class MoviesInSeries(
val originalMovieName: String,
// If there's no value of parameter, assign it as null
val movieInSeries1Name: String? = null,
val movieInSeries1Date: String? = null,
val movieInSeries1Rating: String? = null,
val movieInSeries1Pic: Int? = null,
val movieInSeries2Name: String? = null,
val movieInSeries2Date: String? = null,
val movieInSeries2Rating: String? = null,
val movieInSeries2Pic: Int? = null,
)
I've created two objects of it.
fun getRestOfSeriesMovies(): ArrayList<MoviesInSeries> {
val movieList = ArrayList<MoviesInSeries>()
val s_gi_joe = MoviesInSeries("G.I. Joe: Retaliation", "G.I. Joe: The Rise of Cobra",
"2009","Pg-13", R.drawable.gijtsofmp)
movieList.add(s_gi_joe)
val s_gi_joe2 = MoviesInSeries("G.I. Joe: Retaliation2", "G.I. Joe: The Rise of Cobra",
"2009","Pg-13", R.drawable.gijtsofmp)
movieList.add(s_gi_joe)
return movieList
}
Now I want to create an array list of the originalMovieName member. I'll later use that to check if a certain string is in it. How would I do that?
there's a lot of weird things happening in your model class :
1-
checkIFMovieNameExist(getRestOfSeriesMovies())
fun checkIFMovieNameExist(list: List<MoviesInSeries>,value:String): MoviesInSeries? {
return list.find { it.originalMovieName == value }
}
2-from time of creation:
data class MoviesInSeries(
val originalMovieName: String,
// If there's no value of parameter, assign it as null
val movieInSeries1Name: String? = null,
val movieInSeries1Date: String? = null,
val movieInSeries1Rating: String? = null,
val movieInSeries1Pic: Int? = null,
val movieInSeries2Name: String? = null,
val movieInSeries2Date: String? = null,
val movieInSeries2Rating: String? = null,
val movieInSeries2Pic: Int? = null,
){
init {
names.add(originalMovieName)
}
companion object {
private val names: MutableList<String> = mutableListOf()
fun getMoviesNames():List<String>{
return names
}
}
}
then you can check if a name exist in names list in any place in the code
MoviesInSeries("sd1")
MoviesInSeries("sd2")
MoviesInSeries("sd3")
MoviesInSeries("sd4")
println(MoviesInSeries. getMoviesNames().contains("sd1"))
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))
}
}
Bear with me, it's a tricky question and what resources I've found around don't really help me resolve my problem.
I'm trying to build a real estate-oriented app on Kotlin. It must show at some point a RecyclerView with multiple object classes (say: houses, flats, plots, buildings, etc.)
I've seen multiple examples of RVs designed to accept multiple classes, but I'm struggling to put together a DB and the intermediary classes translating between tables and POJOs.
So far I've figured the following:
I must have a Properties table that stores the unique ID for every object, along with another identifier for its type and a series of values common to every property (say, address, price, etc.)
I must have a table for each entity type that can be independently listed as a real estate item (say, a house, a flat, a plot of land, a building, what have you). Each row on those tables will have a primary foreign key referencing its equivalent on the Properties table.
Now for the unexpected habanero. I decided to start sketching out my project on the basis of the RecyclerView Kotlin codelabs Google put together for newbies like me. Therein data is retrieved from the DB in this fashion:
this.plots = Transformations.map(database.RealtorDao.getPlots()) { it.asDomainModel() }
This works smoothly enough when the objects on the list the DB spits at you are all of one single kind, but what happens if you need them to be of different classes so that the adapter can tell them apart?
Or the only way around is just to build a gigantic table with about a hundred columns that will have nulls everywhere, and sort out objects ONLY AFTER they've been parsed in the previously described fashion?
I smashed my head against this wall until I got tired of hearing the squishing sound. I could not get a Room DB to return a list of objects of multiple classes, so I had to adopt a dirtier approach.
If I had worked just with the database classes then probably I could have hacked it, but trying to translate objects of such classes into POJOs to use instead complicated things somewhat.
The workaround I found was to make a master real estate class and accept that it would have lots and lots of null fields on the database. While a far cry from ideal, it works.
Database object classes:
open class DatabaseProperty
{
#ColumnInfo(name = COL_TYPE)
#SerializedName(COL_TYPE)
#Expose
var type: String? = null
#ColumnInfo(name = COL_ADDRESS)
#SerializedName(COL_ADDRESS)
#Expose
var address: String? = null
#ColumnInfo(name = COL_OWNER)
#SerializedName(COL_OWNER)
#Expose
var owner: String? = null
#ColumnInfo(name = COL_PRICE_FINAL)
#SerializedName(COL_PRICE_FINAL)
#Expose
var priceFinal: Long? = null
#ColumnInfo(name = COL_PRICE_QUOTED)
#SerializedName(COL_PRICE_QUOTED)
#Expose
var priceQuoted: Long? = null
/**
* No args constructor for use in serialization
*/
constructor()
#Ignore
constructor
(
type: String,
address: String,
owner: String,
priceFinal: Long,
priceQuoted: Long
) : super() {
this.type = type
this.address = address
this.owner = owner
this.priceFinal = priceFinal
this.priceQuoted = priceQuoted
}
}
#Entity
(
tableName = TABLE_RE,
indices =
[
Index(value = [COL_RE_ID], unique = true)
],
foreignKeys =
[
ForeignKey
(
entity = DatabaseRealEstate::class,
parentColumns = arrayOf(COL_RE_ID),
childColumns = arrayOf(COL_PARENT_ID),
onDelete = ForeignKey.NO_ACTION
)
]
)
data class DatabaseRealEstate
(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = COL_RE_ID)
var id: Int? = null,
#ColumnInfo(name = COL_PARENT_ID)
var parent_id: Int? = null,
#Embedded(prefix = RE)
var property: DatabaseProperty? = null,
#ColumnInfo(name = COL_PARCEL_FRONT) // Plot front
#SerializedName(COL_PARCEL_FRONT)
#Expose
var front: Float? = null,
#ColumnInfo(name = COL_PARCEL_SIDE) // Plot side
#SerializedName(COL_PARCEL_SIDE)
#Expose
var side: Float? = null,
#ColumnInfo(name = COL_AREA) // Plot area
#SerializedName(COL_AREA)
#Expose
var area: Float? = null,
#ColumnInfo(name = COL_CATASTER)
#SerializedName(COL_CATASTER)
#Expose
var cataster: String? = null,
#ColumnInfo(name = COL_ZONIFICATION)
#SerializedName(COL_ZONIFICATION)
#Expose
var zonification: String? = null,
)
data class RealEstateWithSubunits
(
#Embedded
val re: DatabaseRealEstate? = null,
#Relation
(
parentColumn = COL_RE_ID,
entityColumn = COL_PARENT_ID,
entity = DatabaseRealEstate::class
)
var subunits: List<DatabaseRealEstate>? = null,
#Relation
(
parentColumn = COL_RE_ID,
entityColumn = COL_PARENT_ID,
entity = DatabaseChamber::class
)
var chambers: List<DatabaseChamber>? = null
)
fun List<RealEstateWithSubunits>.asRESUBDomainModel() : List<RealEstate>
{
return map { obj ->
RealEstate(
id = obj.re!!.id!!,
type = obj.re.property!!.type!!,
address = obj.re.property!!.address!!,
owner = obj.re.property!!.owner!!,
priceFinal = obj.re.property!!.priceFinal!!,
priceQuoted = obj.re.property!!.priceQuoted!!,
parent_id = obj.re.parent_id,
front = obj.re.front,
side = obj.re.side,
area = obj.re.area,
cataster = obj.re.cataster,
zonification = obj.re.zonification,
chambers = obj.chambers!!.asChamberDomainModel(),
subunits = obj.subunits!!.asREDomainModel()
)
}
}
fun List<DatabaseChamber>.asChamberDomainModel(): List<Chamber>
{
return map {
Chamber(
id = it.id,
parent_id = it.parent_id,
front = it.front,
side = it.side,
area = it.area
)
}
}
fun List<DatabaseRealEstate>.asREDomainModel(): List<RealEstate>
{
return map { obj ->
RealEstate(
id = obj.id!!,
type = obj.property!!.type!!,
address = obj.property!!.address!!,
owner = obj.property!!.owner!!,
priceFinal = obj.property!!.priceFinal!!,
priceQuoted = obj.property!!.priceQuoted!!,
parent_id = obj.parent_id,
front = obj.front,
side = obj.side,
area = obj.area,
cataster = obj.cataster,
zonification = obj.zonification,
chambers = ArrayList(),
subunits = ArrayList()
)
}
}
Model object classes:
interface BaseProperty {
var id: Int
var type: String
var address: String
var owner: String
var priceFinal: Long
var priceQuoted: Long
}
data class RealEstate(
override var id: Int = -1,
override var type: String = "",
override var address: String = "",
override var owner: String = "",
override var priceFinal: Long = 0,
override var priceQuoted: Long = 0,
var parent_id: Int?,
var front: Float?,
var side: Float?,
var area: Float?,
var cataster: String?,
var zonification: String?,
var subunits: List<RealEstate>? = null,
var chambers: List<Chamber>? = null
) : BaseProperty
{
fun hasParent() : Boolean
{
if (parent_id == null)
{
return false
}
return true
}
}
I haven't yet found a better approach, so if someone does, I'm welcoming it with open arms.
There are two tables, Task and StatisticsTask for Android room, StatisticsTask has foreign key taskId for Task.id. When I update StatisticsTask, how to tell ui query data again?
#Entity
#Parcelize
data class Task(
var name: String,
var important: Boolean = false,
var startTime: String = timeHm(),
var endTime: String = "",
var remind: String = "",
var repeat: String = "",
var repeatInterval: String = "",
var desc: String = "",
var endDate: String = "",
var startDate: String = nowOfString(),
#PrimaryKey(autoGenerate = true) var id: Int = 0
) : Parcelable {
#Ignore
var selected = false
#Ignore
var time: String? = null
#Ignore
var finished: Boolean = false
}
#Entity(foreignKeys = [ForeignKey(entity = Task::class, parentColumns = arrayOf("id"), childColumns = arrayOf("taskId"),
onDelete = CASCADE)])
#Parcelize
data class StatisticsTask(
var taskId: Int,
var date: Calendar?,
var finish: Boolean = false,
#PrimaryKey(autoGenerate = true)
var id: Int = 0
) : Parcelable {
}
//TaskViewModel.kt
fun finishTask(task: Task) {
launch {
statisticsTaskDao.updateById(task.id, !task.finished)
}
}
#Dao
interface StatisticsTaskDao {
#Query("update statisticstask set finish = :finish where taskId=:taskId and date = :date")
fun updateById(taskId: Int, finish: Boolean, date:Calendar = thisDayInitial())
}
//in my fragment
val model = ViewModelProviders.of(this)[TaskViewModel::class.java]
model.getAllOfToday().observe(this, Observer<List<Task>> {
//udpate data to adapter
})
//I want to finish this task here(in adapter)
iv_finished.setOnClickListener {
//task: first item in adapter
model.finishTask(task)
//This not work. when I finish task, how to update data again?
model.getAllOfToday()
}
This is for android-room 2.2.0-rc1, Android studio 3.5.
How to tell db query data again? I tried call this again is not working: model.getAllOfToday().observe(this#TodayFragment, Observer> {
dispatchData(it)
})
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