I am calling my entity class in an if/else-if statement for 2 different conditions and the values I pass into the parameters depend on the condition. In the if block, I am passing 3 parameters and in the else-if block, I am passing 4. The entity's object is throwing an error because it is expecting 4 parameters. I want the first parameter to be optional and I'd like to know if there is a way to do that in Kotlin.
This is my entity class:
#Entity(tableName = "entry_table")
data class Entry(
#PrimaryKey(autoGenerate = true)
var id: Int? = null,
val username: String? = null,
val hint: String? = null,
val password: String? = null)
And this is the if/else-if block where I'm inserting values into the entity object:
if (requestCode == ADD_ENTRY_REQUEST && resultCode == Activity.RESULT_OK) {
...
val entry = Entry(username, hint, password)
...
} else if (requestCode == EDIT_ENTRY_REQUEST && resultCode == Activity.RESULT_OK) {
...
val entry = Entry(id, username, hint, password)
...
}
In Java, you could solve this problem by creating 2 constructors with matching number of parameters but I wonder if we can do the same in Kotlin or if there is a different approach.
You can try moving the id to the end, like so:
#Entity(tableName = "entry_table")
data class Entry(
val username: String? = null,
val hint: String? = null,
val password: String? = null,
#PrimaryKey(autoGenerate = true)
var id: Int? = null)
And then creating it like:
val entry = Entry(username, hint, password, id)
Or, if you want to keep the id as the first parameter, you can use named arguments like this:
val entry = Entry(username = username, hint = hint, password = password)
Hope that helps!
You can and probably should use multiple constructors.
The downside with your approach is that all the properties must be optional for technical reasons. But I guess, username and password actually are mandatory. The consequences are loss of compile time checks (and therefore prone to NPEs) and cumbersome reading (!! needed when accessing username).
You can avoid these problems following this approach:
To make your class have the default no-arg constructor, that is required by JPA, use the compiler plugin https://kotlinlang.org/docs/reference/compiler-plugins.html#jpa-support
Make mandatory properties non-nullable (I assume, only hint is optional)
Use as many secondary constructors as you like
#Entity(tableName = "entry_table")
data class Entry(
#PrimaryKey(autoGenerate = true)
val id: Int,
val username: String,
val hint: String?,
val password: String
) {
constructor(
username: String,
hint: String = null,
password: String = "change it"
) : this(0, username, hint, password)
}
In a more realistic scenario, only id and username were immutable. password and hint should be allowed to change.
#Entity(tableName = "entry_table")
data class Entry(
#PrimaryKey(autoGenerate = true)
val id: Int,
val username: String
) {
constructor(
username: String,
) : this(0, username)
var hint: String? = null
var password: String = "change it"
}
Related
I'm using GSON to serialize some platform data. When I use #SerialName to capture platform data with a different naming convention in my app, it works for other types, but not Boolean types. As a simple example, if I have a class like...
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
#Serializable
data class Person (
#SerialName("first_name") val firstName: String? = null,
#SerialName("last_name") val lastName: String? = null,
val age: Int? = null
)
... everything works fine. The serializer finds first_name, last_name and age in the data and properly set the properties for the Person.
However, when I try to add a Boolean...
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
#Serializable
data class Person (
#SerialName("first_name") val firstName: String? = null,
#SerialName("last_name") val lastName: String? = null,
val age: Int? = null,
#SerialName("can_sing") val canSing: Boolean? = null
)
... the serializer does not catch and assign can_sing. It is strange that it works with a String but not a Boolean. Can any explain why I am seeing this behavior? I can work around this (for example, I can do val can_sing: Boolean? = null and it works), but I'm just wonder why #SerialName doesn't seem to work for a Boolean, or if I'm just missing something obvious.
You are mixing the Gson and Kotlin annotation types - Gson uses #SerializedName not #SerialName. I am not sure how your string types even work in that case (maybe something in how you call Gson that isn't included in the question).
As an example, the first class here (Person) can be serialized with the Kotlin serialization library, the second with Gson:
Kotlin annotations
#Serializable
data class Person (
#SerialName("first_name") val firstName: String? = null,
#SerialName("last_name") val lastName: String? = null,
val age: Int? = null,
#SerialName("can_sing") val canSing: Boolean? = null
)
Gson Annotations
data class PersonGson (
#SerializedName("first_name") val firstName: String? = null,
#SerializedName("last_name") val lastName: String? = null,
val age: Int? = null,
#SerializedName("can_sing") val canSing: Boolean? = null
)
Examples
Running this unit test with the Kotlin serialization library:
#Test
fun testJsonKotlin() {
val test = Person("hello", "world", 42, false)
val json = Json.encodeToString(test)
println(json)
val t2 = Json.decodeFromString<Person>(json)
println(t2)
}
produces the expected output:
{"first_name":"hello","last_name":"world","age":42,"can_sing":false}
Person(firstName=hello, lastName=world, age=42, canSing=false)
Doing that with Gson
#Test
fun testJsonGsonMixed() {
val testp = Person("hello", "world", 42, false)
val json = Gson().toJson(testp)
println(json)
val t2 = Gson().fromJson(json, Person::class.java)
println(t2)
}
technically works, but ignores the serialized name annotations (for all the cases, not just the boolean)
{"firstName":"hello","lastName":"world","age":42,"canSing":false}
Person(firstName=hello, lastName=world, age=42, canSing=false)
Using the Gson-annotated class with Gson
#Test
fun testJsonGson() {
val test = PersonGson("hello", "world", 42, false)
val json = Gson().toJson(test)
println(json)
val t2 = Gson().fromJson(json, PersonGson::class.java)
println(t2)
}
gives the correct response again
{"first_name":"hello","last_name":"world","age":42,"can_sing":false}
PersonGson(firstName=hello, lastName=world, age=42, canSing=false)
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 an Entity like this:
data class Person(
#PrimaryKey(autoGenerate = true) val id: Long = 0,
#ColumnInfo(name = "user_id", index = true) var userId: Long = 0,
#ColumnInfo(name = "first_name") var firstName: String = "",
#ColumnInfo(name = "mid_name") var midName: String = "",
#ColumnInfo(name = "last_name") var lastName: String = ""
) {
public fun fullName(): String {
return "$firstName $midName $lastName"
}
}
I know that I can set a "not null" attribute to each named property. But in my case, it's no need to fill up all the name properties, I only want to validate the full name is blank or not before this entity saves to the room database.
I'm practicing to use the MVVM framework, but now I'm not sure where should I put the validation. Activity/Fragment? ViewModel? Repository? or inside the Entity directly?
I think maybe I should do this in a repository so that I can prevent the wrong input before I save it to the database. But what if there is another use case that I need to validate the same thing in a different repository? If so, then the code will be duplicated in two repositories.
I've ever written ruby on rails before, there is validation function in the Model like:
# This is ruby on rails code
validate :name_validation
def name_validation
if first_name == "" && mid_name == "" && last_name == ""
errors.add(:name, "the name should not be totally blank")
end
end
I wonder if there is a similar way to validate a property in the Entity, or there is any better practice to resolve this?
Please help me figure it out.
I think maybe we can just use a fullName property directly and let it not null.
Then make a Name class for name processing, and NameConveter class for converting.
So the sample code may seems like this:
class Name(
var firstName: String = "",
var midName: String = "",
var lastName: String = ""
) {
public fun fullName(): String {
return "$firstName $midName $lastName".trim()
}
}
#Entity
data class Person(
#PrimaryKey(autoGenerate = true) val id: Long = 0,
#ColumnInfo(name = "user_id", index = true) var userId: Long = 0,
#ColumnInfo(name = "full_name") var fullName: Name
)
class NameStringConverter {
#TypeConverter
fun fromString(value: String): Name {
val nameStr = value.split(" ")
return Name(nameStr[0], nameStr[1], nameStr[2])
}
#TypeConverter
fun nameToString(name: Name): String? {
val fullName = name.fullName()
// Here is the trick
// to make a blank full name become an invalid name for a not null property
if (name.isNullOrBlank()) {
return null
} else {
return fullName
}
}
}
So that we can access multiple name by Name Class, and database will help us to check the fullname now.
I know that my sample code may cause some problem such as an extra blank character in a name string, but let us focus on the fullName validation issue for now.
I figured out this solution today, I'm not sure it's a good answer or not.
I post here and open for everyone to judge it.
Hi I have a Kotlin data class as follows
data class User (
#get:Exclude val gUser: Boolean,
#get:Exclude val uid: String,
#get:PropertyName("display_name") val displayName: String,
#get:PropertyName("email") val email: String,
#get:PropertyName("account_picture_url") val accountPicUrl: String,
#get:PropertyName("provider") val provider: String
)
I am able to serialize the object without an issues. But i'm having trouble deserializing the object when doing a firebase query. Currently this is what i'm doing to get the data
_firebaseReference.child(getString(R.string.firebase_users_key)).child(user.uid)
.setValue(user).addOnCompleteListener{
_firebaseReference.child("users").child(user.uid)
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(p0: DataSnapshot) {
if (p0.exists()) {
val userHash = p0.value as HashMap<*, *>
var currentUser: User
if (userHash[getString(R.string.provider_key)]
!= getString(R.string.provider_google)) {
currentUser = User(false, p0.key!!,
userHash["display_name"].toString(),
userHash["email"].toString(),
userHash["account_picture_url"].toString(),
userHash["provider"].toString())
} else {
currentUser = User(true, p0.key!!,
userHash["display_name"].toString(),
userHash["email"].toString(),
userHash["account_picture_url"].toString(),
userHash["provider"].toString())
}
}
}
})
}
This is only a test project that i'm working on to practice my Kotlin, but this is something I would like to figure out.
If i'm doing it completely wrong please let me know, any advise would be greatly appreciated
Thanks
Firebase needs an empty constructor to be able to deserialize the objects:
data class User(
#Exclude val gUser: Boolean,
#Exclude val uid: String,
#PropertyName("display_name") val displayName: String,
#PropertyName("email") val email: String,
#PropertyName("account_picture_url") val accountPicUrl: String,
#PropertyName("provider") val provider: String
) {
constructor() : this(false, "", "", "", "", "")
}
You can either declare it like so and provide some default values to be able to call the primary constructor or you can declare default values for all your parameters:
data class User (
#Exclude val gUser: Boolean = false,
#Exclude val uid: String = "",
#PropertyName("display_name") val displayName: String = "",
#PropertyName("email") val email: String = "",
#PropertyName("account_picture_url") val accountPicUrl: String = "",
#PropertyName("provider") val provider: String = ""
)
Then various constructors will be created for you, including an empty constructor.
If there's a problem with serialization there might be because of the getters and setters generated by the ide, try reinforcing them with #get and #set annotations:
data class User (
#Exclude val gUser: Boolean = false,
#Exclude val uid: String = "",
#set:PropertyName("display_name")
#get:PropertyName("display_name")
var displayName: String = "",
#PropertyName("email") val email: String = "",
#set:PropertyName("account_picture_url")
#get:PropertyName("account_picture_url")
var accountPicUrl: String = "",
#PropertyName("provider") val provider: String = ""
)
What I actually wanted is a Kotlin data class which is derived from a domain model interface like so
data class Dto(#PropertyName("serialized_title") val override title: String) : DomainModel
In this case DomainModel is defined this way
interface DomainModel { val title: String }
My goal was to fetch data from Firestore and get deserialized Dto objects which are provided to clients which receive objects of type DomainModel. So this solution above unfortunately didn't work. I saw the workarounds using #get: and #set: Annotations but I wanted my data class properties to be immutable. Simply using vars is a bad design decision in my use case. And also this solution looks quite ugly...
After inspecting the decompiled Java-Code I came up with this solution
data class Dto(
#field:[JvmField PropertyName("serialized_title")]
override val title: String = "") : DomainModel
The decompiled Java-Code simply uses title as public final field having the PropertyName annotation.
I prefer this solution since it doesn't violate certain design decisions I made...
In Android Studio (kotlin)
use this (only var and getter and setter):
#set:PropertyName("email") #get:PropertyName("email") var emailPerson: String = ""
None of this works:
#PropertyName("email") var emailPerson: String = ""
#PropertyName("email") val emailPerson: String = ""
#get:PropertyName("email") val emailPerson: String = ""
Android Studio 4.1.2. Gradle: com.google.firebase:firebase-database:19.6.0
I have a scenario. I have created a data class in Kotlin like this:
data class AgentDetails(
val mobileNo: String,
val Name: String,
val Email: String,
val Password: String,
val Occupation: String,
val pincode: String,
val usertype: String,
val profilepic: String,
val AccountStatus: String
)
I want to send different type of objects of this data class to a web service:
1st object example:
val agentDetails = AgentDetails(mobileNo = mobileNumberText.text.toString(),
Name = userNameText.text.toString(),
Email = emailIdText.text.toString(),
Password = HashUtils.sha1(passwordText.text.toString()),
Occupation = item,
pincode = pinCodeText.text.toString(),
usertype = "Agent",
profilepic = "null", AccountStatus = "pending")
In 2nd object I only want to send mobile number. I dont wanna include any other field. Something like this:
val agentDetails = AgentDetails(mobileNo = mobileNumberText.text.toString())
And in 3rd object I only wanna send email id. Instead of creating multiple data classes. Can I use the same data class for multiple implementations?
Personally, I'd define three objects because they represent three different concepts (or projections of a concept). But if you make your properties nullable and provide a default value of null, you can get away with creating them as you want...
data class AgentDetails(
val mobileNo: String? = null,
val name: String? = null,
val email: String? = null,
val password: String? = null,
val occupation: String? = null,
val pincode: String? = null,
val usertype: String? = null,
val profilepic: String? = null,
val accountStatus: String? = null
)
Note: I've changed some of your property names to camelCase, as is the proper convention. And these all work fine:
AgentDetails(mobileNo = mobileNumberText.text.toString())
AgentDetails(email = "foo#example.com")
AgentDetails(name = "Foo", password = "Bar")
All of the other fields not provided will be null, and the types will be nullable, so you'll have to guard against that. Otherwise, I'd define three data classes for this.
Another solution would be to consider a sealed class structure:
sealed class AgentDetails
data class AgentByName(val name: String) : AgentDetails()
data class AgentByEmail(val email: String): AgentDetails()
// etc..
And then use it in a when expression:
fun doSomethingWithAgents(agentDetails: AgentDetails) {
when (agentDetails) {
is AgentByName -> // Do something
is AgentByEmail -> // Do Something
}
}
The easiest way is to make the fields nullable and provide default values:
data class AgentDetails(
val mobileNo: String? = null,
val Name: String? = null,
val Email: String? = null,
val Password: String? = null,
val Occupation: String? = null,
val pincode: String? = null,
val usertype: String? = null,
val profilepic: String? = null,
val AccountStatus: String? = null
)