I'm using Kotlin and Realm to write a data class
data class AuthToken(val register: Boolean,
val token: String,
val tokenSecret: String,
val user: AuthUser)
I have to save the data to db, so I use Realm to save it. But as we know, if I want to save the class to Realm, the AuthToken class has to extend RealmObject.
That's the problem, Kotlin says data classes can't extend classes.
so I give up data class, just using a normal Kotlin class as a model then another question comes:
Kotlin class has no getter or setter. As we know Realm class have to set all the property private and write getter and setter.
Now i'm wondering how to solve the problem.
Realm doesn't support Data classes currently. You can see an example of how to write Realm compatible model classes in Kotlin here: https://github.com/realm/realm-java/tree/master/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model
public open class Person(
#PrimaryKey public open var name: String = "",
public open var age: Int = 0,
public open var dog: Dog? = null,
public open var cats: RealmList<Cat> = RealmList(),
#Ignore public open var tempReference: Int = 0,
public open var id: Long = 0
) : RealmObject() {
Any Kotlin property in any class has a getter and a setter. So I believe you code should just work as yourself suggested (without data modifier).
https://kotlinlang.org/docs/reference/data-classes.html#data-classes
https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#properties
P.S. I agree that the docs on properties are unclear on the subject
Related
How to get the copy of arraylist of sealed class in android
private var homePageApiResponseList : ArrayList<HomeApiResponseModel> = ArrayList()
Here HomeApiResponseModel is a Sealed class. HomeApiResponseModel is given as Below
sealed class HomeApiResponseModel {
data class HomeCategoryListModel(
var categoryList : MutableList<CategoryModel> = mutableListOf(),
var categoryNameType : String = ""
) : HomeApiResponseModel()
data class HomeBestSellerListModel(
var bestSellerList : MutableList<ChildrenModel> = mutableListOf(),
var bestSellerNameType : String = ""
) : HomeApiResponseModel()
data class HomeMustTryListModel(
var mustTryList : MutableList<ChildrenModel> = mutableListOf(),
var mustTryNameType : String = ""
) : HomeApiResponseModel()
}
Normally arraylist of object copy is easly obtain by anyList.map { it.copy() }
While in sealed class it shows error. How to get a copy of arraylist of sealed class
Thanks
Create an abstract function in the parent. Each child can implement it and call through to their own copy(). The abstract function should have a different name than “copy” to avoid conflicts.
By the way, in your case, a sealed interface is probably a cleaner choice than a sealed class because there is no common functionality between the children. And I suggest avoiding combining mutable collections with var properties. Making something mutable in two different ways adds (usually unnecessary) complexity and more opportunities for bugs.
I have an activity which uses a ViewModel class to store and manage UI-related data. The view model class used in the activity has a structure similar to the one given below:
class SomeViewModel:ViewModel(){
#PrimaryKey(autoGenerate=true)
var id=0
var field1:Array<Double?>=arrayOf(null,null)
var field2:Array<Double?>=arrayOf(null,null)
var field3:Array<Double?>=arrayOf(null,null)
#Ignore
val someFragments=HashMap<String,Fragment>()
#Ignore
val someMap=HashMap<String,Int>()
}
What I am trying to do is to save the data in the view model object to the local database when back is pressed to close the activity. But the issue is when I am trying to do so I am getting an error.
Cannot figure out how to save this field into database. You can consider adding a type converter for it. - mBagOfTags in androidx.lifecycle.ViewModelerror: Cannot find getter for field. - mBagOfTags in androidx.lifecycle.ViewModelerror: Cannot find getter for field. - mCleared in androidx.lifecycle
I am using a TypeConverter identical to the one given below :
class Converter{
#TypeConverter
fun fromArrayDouble(field:Array<Double?>):String{
val s=StringBuilder("")
var first=true
for(k in field){
s.append(k.toString())
if(first){
s.append(",")
first=false
}
}
return s.toString()
}
#TypeConverter
fun fromString(str:String):Array<Double?>{
val parts=str.split(',')
val res=ArrayList<Double?>()
for(p in parts){
try{
res.add(p.toDouble())
}catch(e:Exception){
res.add(null)
}
}
return res.toTypedArray()
}
}
and a database class similar to :
#Database(entities=[SomeViewModel::class], version=1, exportSchema=false)
#TypeConverters(Converter:class)
abstract class SomeDatabase:RoomDatabase(){
// database definition
}
I don't understand where I am going wrong. Any help will be highly appreciated.
Please refer to this answer from another post.
Kotlin room does not allow defining variables in entity class start of 'i' character. I had the same error. I solved it, albeit difficult. Replace id with pId and it will be fine.
Or probably you need to add getter to be found by Room.
fun getId(): Int {
return id
}
According to the official doc, this is the way to declare a data class
data class User(val name: String, val age: Int)
but most cases i see where data class is declared thus,
data class User(var username: String? = "", var email: String? = "", var age: Int = 0)
explanation from the docs: On the JVM, if the generated class needs to have a parameterless constructor, default values for all properties have to be specified
i don't understand, and what are the implications of the different methods
2ndly, what is the best way of writing a data class containing complex object variables such as, ArrayList, Bitmap, Uri
A parameterless constructor would probably only be necessary if you were using the class with some library that generates instances of your class through reflection, such as a serialization library or dependency injection library.
If you put = and some value after a constructor parameter, it allows you to omit that parameter when calling the constructor. The value you put there will be the default value used when the parameter is omitted. This is usually used as a convenience. But if you are using one of the aforementioned libraries, you would also need an empty constructor. By providing defaults for every parameter and annotating the primary constructor with #JvmOverloads, you can satisfy this requirement. It would look like this:
data class User #JvmOverloads constructor(var username: String? = "", var email: String? = "", var age: Int = 0)
If your data class has a lot of properties, you may not want to use #JvmOverloads because the number of constructors it generates is exponentially proportional to the number of properties with defaults. Instead, you can define a secondary constructor like this:
data class User(var username: String?, var email: String?, var age: Int) {
constructor(): this("", "", 0)
}
Your second question is very open-ended and opinion-based, so near-impossible to answer in a helpful way.
Is there a way to subclass entities in Room?
I have an car entity
#Entity(tableName = "cars")
data class Car(
#PrimaryKey #ColumnInfo(name = "vin") val vin: String,
val manufacturer: String,
val model: String,
val color: String,
val modelYear: Int,
val trim: String = ""
) {
override fun toString() = String.format("%s %s %s %s", modelYear, manufacturer, model, trim)
}
But I want to move the manufacturer, model, and modelYear to a Vehicle entity and have the Car inherit from it.
I tried creating a Vehicle entity with those fields and use data class Car : Vehicle, but does not compile. The error is This type is final and cannot be inherited
In kotlin, all classes are final by default.
From Docs
The open annotation on a class is the opposite of Java's final: it
allows others to inherit from this class. By default, all classes in
Kotlin are final, which corresponds to Effective Java, 3rd Edition,
Item 19: Design and document for inheritance or else prohibit it.
so you need to add open keyword so use
open class Vehicle(..){...}
then
data class Car(...): Vehicle(..){}
Side Note: if you trying to inherit data classes then simply you cannot inherit data class in Kotlin because method like copy in data class are final which cannot be overridden by the child class (which in this case is data class so it will do it automatically) and it's hard to implement other methods like equals when class hierarchy grows exponentially with different data members though you can avoid all this collision by making non-data parent class or abstract class
I've been moving into using Room, and I've run into a blocking issue. I've gone through and fixed all of the compile-time checks from the Room library, but am now encountering the following error:
Entities and Pojos must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type).
This appears twice at compile time with no evidence of which class this comes from, but I was able to figure out (by removing classes from the Database) that this was one of the files. I'm assuming it has something to do with the Primary Key being a string instead of an Int (this is one of two classes that uses this), but nothing in the documentation indicates what the issue would be, and in fact the documentation shows that strings are valid Primary Keys.
#Entity(tableName = "inspections")
data class Inspection(
#SerializedName("id")
var id: Int = 0,
...
// Rest of code left off for brevity, found to not be related to the issue.
I've tried a few things to try and get around this.
Remove the data attribute of this class to make it a normal POKO
Remove the variables from the default constructor, and place them into the class
Remove the Ignore from the empty constructor (note, this causes a different issue, Room cannot pick a constructor since multiple constructors are suitable - the Ignore annotation on a default constructor gets around this.) This is the part which perplexes me the most - removing this says "multiple constructors are valid", keeping it says "no constructors are valid".
Updated: Adding a few more relevant code snippets from my project.
build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
.....
implementation 'android.arch.persistence.room:runtime:1.0.0-alpha9-1'
implementation 'android.arch.persistence.room:rxjava2:1.0.0-alpha9-1'
kapt 'android.arch.persistence.room:compiler:1.0.0-alpha9-1'
Database class
#Database(entities =
arrayOf(Account::class, Category::class,
Inspection::class, InspectionForm::class,
InspectionFormItem::class, InspectionFormsStructure::class,
InspectionItemPhoto::class,
InspectionItem::class, LineItem::class,
LocalPhoto::class, Rating::class,
Structure::class, SupervisoryZone::class,
Upload::class, User::class),
version = 16)
#TypeConverters(Converters::class)
abstract class OrangeDatabase : RoomDatabase() {
abstract fun inspectionDao(): InspectionDao
abstract fun localDao(): LocalDao
abstract fun ratingsDao(): RatingsDao
abstract fun structureZoneDao(): StructureZoneDao
abstract fun userAccountDao(): UserAccountDao
}
Converters
class Converters {
#TypeConverter
fun fromTimestamp(value: Long?): Date? {
return if (value == null) Date() else Date(value)
}
#TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time ?: 0
}
#TypeConverter
fun fromStringToArray(value: String?): Array<String>? {
return value?.split(",")?.toTypedArray() ?: arrayOf()
}
#TypeConverter
fun stringToStringArray(strings: Array<String>?): String? {
return strings?.joinToString(",") ?: ""
}
}
Another data class
#Entity(tableName = "users")
data class User(
#PrimaryKey
#SerializedName("id")
var id: Int = 0,
...
// Rest of code left off for brevity, found to not be related to the issue.
UserPermissions class:
data class UserPermissions(
#SerializedName("id")
var pid: Int = 0,
...
// Rest of code left off for brevity, found to not be related to the issue.
The problem in your case is, that if you have nullable values Kotlin will generate several constructors for each possible constructor.
That means that you have to define a default constructor and fill it with default values.
If you want to have another one which should be ignored you should make sure to use the parent constructor with all those parameters.
Example:
#Entity(tableName = "inspections")
data class Inspection(
#SerializedName("id")
var id: Int = 0,
#PrimaryKey
#SerializedName("guid")
var guid: String = "",
#SerializedName("score")
var score: Double = 0.0,
#SerializedName("notification_sent_at")
var notificationSentAt: Date = Date(),
var wasUploaded: Boolean = false) {
#Ignore
constructor() : this(0, "", 0.0, Date(), false)
}
In this case only two constructors will be generated "under the hood". If you have nullable values you will have all possible constructors available.
Example:
data class Test(var id: Int = 0, var testString: String? = null, var testBool : Boolean? = null) {
constructor(0)
}
generates
constructor(var id:Int)
constructor() : this(0)
constructor(var id:Int, var testString: String)
constructor(var id:Int, var testBool: Boolean)
constructor(var id:Int, var testString: String, var testBool : Boolean)
// .. and so on
Since you'r looking for an official documentation, you may want to look at Overloads Generation.
After testing your class which works flawlessly i found in another post that you have to check if you used apply plugin: 'kotlin-kapt' in your Gradle.
Double check that you've valid type converters for your Date class. I wrote that issue longer time ago.
After recoding your stuff above it worked just fine by adding a UserPermissions class like that:
data class UserPermissions(var permissionid: String)
Edit: After using your UserPermission class everything worked just fine. Please take care if you use the proper import (util.Date instead of sql.Date for example).
Another problem is that your using an old very buggy library of room.
The current version (while writing this) is
implementation "android.arch.persistence.room:runtime:1.0.0-beta2"
kapt "android.arch.persistence.room:compiler:1.0.0-beta2"
implementation "android.arch.persistence.room:rxjava2:1.0.0-beta2"
I wrote an issue long time ago
The issue was extremely difficult to debug and harder to reproduce, but I found the issue. I was using an #Embedded object, but the result that was going in was actually a List of that object. This was giving trouble to the automatic Embed task, and there wasn't a perfect Converter that could be written for it.
#SerializedName("range_choices")
#Embedded
var rangeChoices: List<RangeChoice>? = null,
I had to annotate that with #Ignore and instead, I'll be saving the results of this list to its own table, now the new table range_choices.
Your Primary keys should be like given below using Annotation Use-site Targets
#field:PrimaryKey #field:SerializedName("guid") var guid: String = ""
and
#field:PrimaryKey #field:SerializedName("id") var id: Int = 0
Try to avoid using nullable values and make everything have some kind of default value. That's the simpliest way to solve this issue.
If you really want to use them, then you may create a constructor, containing all of them.