I have a data class that is used in Room. At the head of the class there are date fields in the form of a string _dateControl and _timeCreate. There are dateControl and timeCreate fields of type Long in the body of the class. When requesting data via Retrofit, the dateControl and timeCreate fields are initialized independently. But if I try to declare an instance of a class, then these fields(dateControl and timeCreate) are not initialized and equal to 0.
Class
#Entity
class DetailNum(
#SerializedName("id_ndt_res")
#PrimaryKey val id: Int,
#SerializedName("id_pr")
val idEntUser: Int,
#SerializedName("id_parts")
val type: Int,
#SerializedName("dt_controlya")
var _dateControl: String,
#SerializedName("number_part")
var numDetail: String?,
#SerializedName("god_izg")
var yearCreate: Int,
#SerializedName("id_zavods")
var idFactory: Int,
#SerializedName("time_create")
var _timeCreate: String,
#SerializedName("id_result")
var resultControl: Int,
#SerializedName("familiya_i_o_speca")
var fioSpec: String?,
#SerializedName("is_new_part")
var isNew: Int,
#SerializedName("is_from_pto")
var isFromPto: Int,
#SerializedName("is_not_kriterii_01_08_2013")
var isNotKrit01082013: Int,
#SerializedName("isLoaded")
var isLoaded: Int = 0
) {
public fun getSimpleDateStr() = SimpleDateFormat("dd.MM.yyyy").format(Date(dateControl))
val strResultControl: String
get() = resultControl.toString()
var dateControl: Long = 0
set(value) {
val date = SimpleDateFormat("dd.MM.yyyy").parse(_dateControl)
field = date.time
}
var timeCreate: Long = 0
set(value) {
val date = SimpleDateFormat("dd.MM.yyyy HH:mm:ss").parse(_timeCreate)
field = date.time
}
var nameResult:String?=""
fun getFirstCharNameResult() = if(!nameResult.isNullOrEmpty()) nameResult?.get(0)?.toUpperCase() else ""
}
Try inicialize like this
var detailNum = DetailNum(0, 0, 0, DateUtils.getSimpleDateStr(Date()), "", 0, 0, DateUtils.getSimpleDateStr(Date()), 0,"", 0, 0, 0 )
Why are long date fields not initialized in the second case and how to initialize them? I am new in Kotlin, please explain, thanks in advance
Your fields like dateControl and timeCreate are not initialised in the constructor! Their setter don't get called automatically if not by some framework.
If you want them to be set right after the constructor initialisation, you can either:
1) .. set default value immediately
val dateControl: Long = SimpleDateFormat("dd.MM.yyyy").parse(_dateControl).time
2) .. or use init if the operation needs more code or you want to gather inner field initialisations together
class DetailNum(...) {
var dateControl: Long = 0
init {
dateControl = SimpleDateFormat("dd.MM.yyyy").parse(_dateControl).time
...
}
}
More infoirmation about constructors and init you can find here.
BTW: your setters don't use the provided value at all, so actually they are useless.
use lateinit to the variables like below
lateinit var subject: TestSubject
Related
I have a global object that store 2 custom class objects, something like this:
/////// Separated object file ////////
object ApplicationData {
var profiledata:ProfileData = ProfileData(null)
var OVOlists = ArrayList<OVOList>()
}
/////// Separated class file ////////
class OVOList (
private var OVOIcon: Int,
private var OVOName: String?,
private var OVOPlace: String?,
private var OVOstatus: Int
) :Parcelable {
private var Humidity:String? =""
private var Temperature:String? = ""
private var PH:String? =""
private var WaterLvl :String? = ""
constructor(parcel: Parcel) : this(
parcel.readValue(Int::class.java.classLoader) as Int,
parcel.readString(),
parcel.readString(),
parcel.readValue(Int::class.java.classLoader) as Int
) {
Humidity = parcel.readString()
Temperature = parcel.readString()
PH = parcel.readString()
WaterLvl = parcel.readString()
} ... + setters , getters and parcel lines
/////// separated class file ////////
class ProfileData(private var email:String?):Parcelable {
private var token:String? =""
private var profile_image: String? =""
private var profile_image_path:String? = ""
private var nombre:String? = ""
private var uid:String? = ""
constructor(parcel: Parcel) : this(
parcel.readString(),
) {
token=parcel.readString()
profile_image = parcel.readString()
profile_image_path = parcel.readString()
nombre = parcel.readString()
uid=parcel.readString()
} ... + setters,getters and parcel lines
The classes are parcelable, because i was moving some information via bundles, but now im using this global object so there is no need to keep them like that.
But the question is how to store the whole object into memory, i have try the Gson/preferences aproach but i cannot make it work :(, it doesnt store the object, maybe its because it has 2 custom class objects inside, i dont think the parcelable attribute should affect. I made something like this:
//write//
mprefs.edit().putString("MyappData",gson.toJson(ApplicationData)).apply()
//read//
String json = mprefs.getString("MyappData", "")
val obj = gson.fromJson(json, ApplicationData::java.class)
ApplicationData.profiledata = obj.profiledata
ApplicationData.OVOlists = obj.OVOlists
It seems that its failing in the writing part , any ideas what to do?
You can save them as Strings (json format ) and the covert to Object ,as you are doing , I think you should use data classes and use #Parcelize and avoid using too much code
Thanks to all your help, i made this work with Gson, i created inside the object a function that stores and retrieves the data from/to pref. i just pass the context to it, i think was not working properly because of the parcelable part inside the classes. The final object is this:
object ApplicationData {
var profiledata:ProfileData = ProfileData(null)
var OVOlists = ArrayList<OVOList>()
fun save(context: Context){
val sharedPreference = context.getSharedPreferences("OVO_PREF", Context.MODE_PRIVATE)
val prefsEditor = sharedPreference.edit()
val gson = Gson()
val json = gson.toJson(ApplicationData)
prefsEditor.putString("AppData", json)
Log.e("saving in preferences",json)
prefsEditor.apply()
}
fun retrieveData(context: Context):Boolean{
val sharedPreference = context.getSharedPreferences("OVO_PREF", Context.MODE_PRIVATE)
val gson = Gson()
val json = sharedPreference.getString("AppData", "")
val obj = gson.fromJson(json, ApplicationData::class.java)
if(obj!=null){
profiledata = obj.profiledata
OVOlists = obj.OVOlists
//Log.e("retrieve info",obj.OVOlists[0].getOVOName()!!)
return true
}else{
return false }
}
The two custom classes now are regular and normal classes, with setters and getters. So the main object can save its own data calling ApplicationData.save(context)
Imagine receiving from an endpoint an object like this
data class Foo(
val name: String,
val start: String,
val end: String
)
where the properties start and end are string in the format of "00:00:00" to represent the hours, minutes and second from midnight.
I need to save this object in a Room table but with start and end parsed as Int instead of string.
The solution I resorted is this:
data class Foo(
val name: String,
val start: String,
val end: String,
var startInSeconds: Int = 0,
var endInSeconds: Int = 0
) {
init {
startInSeconds = this.convertTimeInSeconds(this.start)
endInSeconds = this.convertTimeInSeconds(this.end)
}
private fun convertTimeInSeconds(time: String): Int {
val tokens = time.split(":".toRegex())
val hours = Integer.parseInt(tokens[0])
val minutes = Integer.parseInt(tokens[1])
val seconds = Integer.parseInt(tokens[2])
return 3600 * hours + 60 * minutes + seconds
}
}
But I'd like to avoid to parse the start and end every time.
There is a way to parse before inserting?
you can try my solution if you want ,
first, instead of using the same object Foo to store your data , it is better to create another data class as Entity to encapsulate the data.
data class Bar(
val name: String,
val start: String,
val end: String,
val startInSeconds: Int, // <<-- using val to make data read only
val endInSeconds: Int // <<-- using val to make data read only
)
you might need to create a thousand of object depend on your data size, so using companion object seems to be a good idea to parse the data to avoid unnecessary memory allocation .
data class Bar(
val name: String,
val start: String,
val end: String,
val startInSeconds: Int,
val endInSeconds: Int
) {
companion object {
// overloading the invoke function
operator fun invoke(foo:Foo) : Bar {
return Bar(
name = foo.name,
start = foo.start,
end = foo.end,
startInSeconds = convertTimeInSeconds(foo.start),
endInSeconds = convertTimeInSeconds(foo.end)
)
}
private fun convertTimeInSeconds(time: String): Int {
val tokens = time.split(":".toRegex())
val hours = Integer.parseInt(tokens[0])
val minutes = Integer.parseInt(tokens[1])
val seconds = Integer.parseInt(tokens[2])
return 3600 * hours + 60 * minutes + seconds
}
}
}
//how to use
fun insertToDatabase(foo:Foo){
val parsedData = Bar(foo)
dao.insert(parsedData)
}
I have an entity class (PipeLine) that has a Mutable list of a parcelabel class (DamagePoint )
the main class PipeLine has a field val id:Int=0, primary key set to autoGerat=true it's working fine
the subclass DamagePoint also has a primary key val no:Int=1, I use it for points sequence it's not working!!
all the points generated has the no=0
Just to clarify, each PipeLine has a list of DamagePoints, sequenced by numbers
how would I do that !!
the class PipeLine.kt
#Parcelize
#Entity(tableName = "table_Lines")
data class PipeLine(
#PrimaryKey(autoGenerate = true)
val id:Int=0,
var name: String?,
var ogm: String?,
var length: String?,
var type: String?,
var i_start: String?,
var i_end: String?,
var start_point: String?,
var end_point: String?,
var work_date:String?,
var points: MutableList<DamagePoint>
):Parcelable
#Parcelize
data class DamagePoint(
#PrimaryKey(autoGenerate = true)
val no:Int=1,
var db: String? = null,
var depth: String? = null,
var current1: String? = null,
var current2: String? = null,
var gps_x: String? = null,
var gps_y: String? = null
):Parcelable
class DataConverter {
#TypeConverter
fun fromPoints(points: MutableList<DamagePoint>?): String? {
if (points == null) {
return null
}
val gson = Gson()
val type = object : TypeToken<MutableList<DamagePoint>?>() {}.type
return gson.toJson(points, type)
}
#TypeConverter
fun toPoints(points: String?): MutableList<DamagePoint>? {
if (points == null) {
return null
}
val gson = Gson()
val type = object : TypeToken<MutableList<DamagePoint>?>() {}.type
return gson.fromJson<MutableList<DamagePoint>>(points, type)
}
}
output :
id for PipeLine working and increasing
no for the DamagePoint not working and didn't increase
another output from emulator showing the point list int recyclerView
is the primaryKey don't work in the type converter
or there is another way to do it and make it increase?
please anything would help
I annotate it with #PrimaryKey and set autoGenerate to true
tried to switch between val and var , same thing
The problem is with the 1 integer which you set for the no. In this way, it does not work as auto-increment since there is a specified integer value (1) instead of 0.
So it means that if you set no to 0, it will work as unset and will increase the values.
val no:Int = 0
Also, you may need to change the no name to id.
the problem is I was trying to add a full list of DamagePoint every time I wanted to add a point to the points list in the PipeLine entity so I fetch the list from the object add new points to it and update the entire list for that pipeline object so it's normal to find all the point (no) equal to 1 because it's the default value for it
now I handle the no of each point I add to the list before updating it and adding it back to the pipeline object
I found a way to update only the points list in the pipeline object without updating the entire object
#Query("UPDATE table_Lines SET points=:points WHERE id=:id ")
fun updatePointsList(id:Int,points:MutableList)
if someone knows how to add an item to a mutable list which is a property of the entity class
without updating the list or updating the entity object
please add an answer
I have a JSON string that I need to converted to data class object in Kotlin, the problem is that there is a field (details) that can have a different structure depending of the value of another field like this
val jsonString1 = "{'name': 'Juan', 'phase': 'step1', 'details': { 'name': 'product 1' }}"
val jsonString2 = "{'name': 'Juan', 'phase': 'step2', 'details': { 'position': 10 }}"
now I have something like
data class Customer(
var name: String? = null
var phase: String? = null
var details: Details? = null
)
data class Details(
var name: String? = null
)
data class Details2(
var position: Int? = null
)
now with gson I know I can
Gson().fromJson(jsonString1, Customer::class.java)
I want to be able to automatically use the right data class depending on the value of the phase field, I know I can create an adapterFactory, but I can't figure out how, an in kotlin is worse
I was reading this post
http://anakinfoxe.com/blog/2016/02/01/gson-typeadapter-and-typeadapterfactory/
and I'm pretty sure is the way to go, but I can't quite get it
Yep, it's pretty easy to write such adapter. I've slightly changed your example:
data class Customer(
var name: String? = null,
var phase: String? = null,
var details: Details? = null
)
sealed class Details {
data class Details1(var name: String? = null) : Details()
data class Details2(var position: Int? = null) : Details()
}
class CustomerDeserializer : JsonDeserializer<Customer> {
override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): Customer {
val customerObject = json.asJsonObject
val detailsObject = customerObject.getAsJsonObject("details")
val details = if (detailsObject.has("name")) {
Details.Details1(detailsObject.get("name").asString)
} else {
Details.Details2(detailsObject.get("position").asInt)
}
return Customer(
name = customerObject.get("name").asString,
phase = customerObject.get("phase").asString,
details = details
)
}
}
fun main() {
val gson = GsonBuilder()
.registerTypeAdapter(Customer::class.java, CustomerDeserializer())
.create()
println(gson.fromJson(jsonString1, Customer::class.java))
println(gson.fromJson(jsonString2, Customer::class.java))
}
data class Customer(
var name: String? = null
var phase: String? = null
var details: Details? = null
)
data class Details(
var name: String? = null
var position: Int? = null
)
Define Details class in this way
Gson().fromJson(jsonString1, Customer::class.java)
return a Customer either name is null or position is null
I have been writing an app where at first I declared the class signature as below
data class MClass(val id: Int = 0,val descr: String, val timestamp: Long)
Now a need was created where I must have a custom getter for a field above. How can I write this custom getter? I know that If otherwise I could write something like
data class(){
val id=0
val descr = ""
get() = descr + "append smth"
val timestamp:Long = 0
}
You could do something like below:
data class MClass(val id: Int = 0, private val descr: String, val timestamp: Long) {
val description: String
get() = descr + "append smth"
}
You can make it like that:
data class MClass(val id: Int = 0, private val _descr: String, val timestamp: Long) {
val descr: String
get() = _descr + "append something"
}