I started using the data binding library 2 months ago and really like it. But I'm running into an issue. I have for example the following POJO's:
Car{
#Bindable
var name: String
#Bindable
set(value){
field = value
notifyPropertyChanged(BR.name)
}
}
Bike{
#Bindable
var name: String
#Bindable
set(value){
field = value
notifyPropertyChanged(BR.name)
}
}
So basically I have a Car and Bike POJO that share a property name. They both have a name. The problem is that the BR class will only have one of them, it does not generate a prefix for the class. I tested this and the notifyPropertyChanged method for one of them will not work.
This is really annoying. Now I have to put a prefix on both or one of them so they don't match. This solves the problem but then the Bike class becomes:
Bike{
#Bindable
var bikeName: String
#Bindable
set(value){
field = value
notifyPropertyChanged(BR.bikeName)
}
}
This works but I of course do not want to implement my models like this. Anyone that knows how to solve this?
You could use Kotlin Inheritance. You have common fields for both classes.
open class Vehicle : BaseObservable() {
#Bindable
var name: String = ""
#Bindable
set(value) {
field = value
notifyPropertyChanged(BR.name)
}
}
class Bike : Vehicle() {
}
class Car : Vehicle() {
}
Related
In Kotlin, using a scope function such as "with" that allows the this reference inside the block to reference the lambda result, is it possible to reference the outer class member when it has the same name as one of the fields in the result?
eg
data class Person(name: String)
...
class MyClass {
var name = ""
with(personRepository.getPerson(personId)) {
// How do we set the class "name" member - "this.name" or just "name" refers to the scoped object?
name = this.name // ???
}
Obviously using a different variable name is the simple workaround but just wondering if there is a syntax for when the variables have the same name
class MyClass {
var personName = ""
...
with(personRepository.getPerson(personId)) {
personName = this.name
}
the this refers to the object that you pass. In order for you to refer to the context of your class you would need to use labels like below. Do note that this is an anti pattern. With scoping functions you would only want to apply logic on the object that you are passing or that is the receiver object in scoping functions like let and apply
class MyClass {
var name: String = ""
val person = Person("my name")
fun setName() = with(person) {
this#MyClass.name = person.name
}
}
I would suggest using let{} instead of with{}:
data class Person(name: String)
...
class MyClass {
var name = ""
personRepository.getPerson(personId).let {
name = it.name
}
You can use a labeled this to refer to the variable in MyClass. Like this:
with(personRepository.getPerson(personId)) {
this#MyClass.name = name
}
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
}
I have response like this :
{
"response":{"numFound":5303,"start":0,"maxScore":6.5102634,"docs":[
{
"id":"10.1371/journal.pone.0000290",
"journal":"PLoS ONE",
"eissn":"1932-6203",
"publication_date":"2007-03-14T00:00:00Z",
"article_type":"Research Article",
"author_display":["Rayna I. Kraeva",
"Dragomir B. Krastev",
"Assen Roguev",
"Anna Ivanova",
"Marina N. Nedelcheva-Veleva",
"Stoyno S. Stoynov"],
"abstract":["Nucleic acids, due to their structural and chemical properties, can form double-stranded secondary structures that assist the transfer of genetic information and can modulate gene expression. However, the nucleotide sequence alone is insufficient in explaining phenomena like intron-exon recognition during RNA processing. This raises the question whether nucleic acids are endowed with other attributes that can contribute to their biological functions. In this work, we present a calculation of thermodynamic stability of DNA/DNA and mRNA/DNA duplexes across the genomes of four species in the genus Saccharomyces by nearest-neighbor method. The results show that coding regions are more thermodynamically stable than introns, 3′-untranslated regions and intergenic sequences. Furthermore, open reading frames have more stable sense mRNA/DNA duplexes than the potential antisense duplexes, a property that can aid gene discovery. The lower stability of the DNA/DNA and mRNA/DNA duplexes of 3′-untranslated regions and the higher stability of genes correlates with increased mRNA level. These results suggest that the thermodynamic stability of DNA/DNA and mRNA/DNA duplexes affects mRNA transcription."],
"title_display":"Stability of mRNA/DNA and DNA/DNA Duplexes Affects mRNA Transcription",
"score":6.5102634},
Now in this I want to get the 'abstract' field. For this I had specified it as String but it gave me error that it the array and can not convert to string.
Now I am not sure how to create object for this which array type I should specify.
I checked that we can use the Type Converters but not able to write the converter for the same.
Following is my object and converter which I tried.
DAO
#Entity(tableName = "news_table")
data class NewsArticles(
#PrimaryKey var id: String = "",
#SerializedName("article_type") var title: String? = null,
#SerializedName("abstract") var description: Array<String>,
#SerializedName("publication_date") var publishedAt: String? = null
)
Type Converter
class Converters {
#TypeConverter
fun fromTimestamp(value: Array<String>?): String? {
return value?.let { String(it) } //error
}
#TypeConverter
fun dateToTimestamp(array: Array<String>): String? {
return array.toString()
}
}
Its giving me error for return line that none of the following functions can be called with arguments supplied.
EDIT :
now I changed defination to ArrayList
#SerializedName("abstract") var description: ArrayList,
and converter to this
class ArrayConverters {
#TypeConverter
fun fromArray(value: ArrayList<String>?): String? {
return value?.let { arrayToString(it) }
}
#TypeConverter
fun arrayToString(array: ArrayList<String>): String? {
return array.toString()
}
}
Now its showing this error : error: Multiple methods define the same conversion. Conflicts with these: CustomTypeConverter
Please help. Thank you.
EDIT 2:
As per answer of richard slond, I have added the converter as
class ArrayConverters {
#TypeConverter
fun to(array: Array<String>): String {
return array.joinToString(" ")
}
#TypeConverter
fun from(value: String): List<String> {
return value.split(" ")
}
}
and added in the database as
#Database(entities = [NewsArticles::class], version = 2, exportSchema = false)
#TypeConverters(ArrayConverters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun newsArticlesDao(): NewsArticlesDao
}
Also in the news article module
#Entity(tableName = "news_table")
#TypeConverters(ArrayConverters::class)
data class NewsArticles(
#PrimaryKey var id: String = "",
#SerializedName("article_type") var title: String? = null,
#SerializedName("abstract") var description: String? = null,
#SerializedName("publication_date") var publishedAt: String? = null
)
Here for descriptionn variable if i have added string I am getting error as the field is begin with array.
and if i have specified as the arraylist it gives the error as can not add this type to the database please try using type converter.
What's missing??
The easiest way to store Collection (like Array, List) data into database is to convert them to String In JSON format, and GSON library (developed by Google) is designed for this situation.
How to Use:
String jsonString;
toJsonButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Gson gson = new Gson();
jsonString = gson.toJson(student); //object -> json
}
});
toObjectButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Gson gson = new Gson();
Student currentStudent = gson.fromJson(jsonString, Student.class); //json -> object
}
});
Reminder: try to put your Collection into an Object (as a member variable), otherwise you may need following extra works:
Get the Type for Your Collection:
TypeToken<List<Integer>> typeToken = new TypeToken<List<Integer>>(){};
List<Integer> retrievedNumbers = gson.fromJson(numbersJsonString, typeToken.getType());
If you really want to do that, you need to find a way to represent an array of strings using a primitive type. The easiest way is to use JSON format. For that reason, in your converter, you need to serialize and deserialize your string array.
As quick solution (which I do not recommend) is the following:
#TypeConverter
fun to(array: Array<String>): String {
return array.joinToString(",")
}
#TypeConverter
fun from(value:String): Array<String> {
return value.split(",")
}
Please be aware, following this path, your strings cannot include commas - but you can use another not so common character as separator
I have been looking at Kotlin official tutorial. I came across the topic called Backing Fields
It says,
Classes in Kotlin cannot have fields. However, sometimes it is necessary to have a backing field when using custom accessors. For these purposes, Kotlin provides an automatic backing field which can be accessed using the field identifier:
var counter = 0 // the initializer value is written directly to the backing field
set(value) {
if (value >= 0) field = value
}
I got the above from this official link
My question is, is the "field" pointing to counter variable ?
Can someone please provide me an example for the backing field or describe me in an understanding word ?
Consider this class
class SomeClass {
var counter: Int = 0
set(value) {
if (value >= 0) field = value
}
}
In Android Studio go to Main menu -> Tools -> Kotlin -> Show Kotlin Bytecode and click Decompile in the Kotlin bytecode panel.
What you see is the equivalent code in Java.
public final class SomeClass {
private int counter;
public final int getCounter() {
return this.counter;
}
public final void setCounter(int value) {
if(value >= 0) {
this.counter = value;
}
}
}
The field keyword allows you to assign a value inside a custom setter. In kotlin counter = 3 will call set(3). So if you would define
var counter=0
set(value){
counter = value
}
It would recursively call itself until your stack is full and your process crashes.
The field keyword assigns the value directly without calling the setter again.
A Backing Field is just a field that will be generated for a property
in a class only if it uses the default implementation of at least one
of the accessors
Backing field is generated only if a property uses the default implementation of getter/setter. If you see the following code with perspective of Java. It looks correct. However in "kotlin" it’ll throw Exception.
class User{
var firstName : String //backing field generated
get() = firstName
set(value) {
firstName = value
}
var lastName : String //backing field generated
get() = lastName
set(value) {
lastName = value
}
val name : String //no backing field generated
get() = "{$firstName $lastName}"
var address : String = "XYZ" //^because there is no default //^implementation of an accessor
}
In Kotlin the above code snippet will throw StackOverflow because when we access or set property "first-name" or "last name" the default accessor will be called. So in Kotlin "user.firstName = "value"” is same as Java’s "user.setFirstName("value")".
So when "set(value) {firstName = "value"} " is called, then a recursive callhappens and compiler throws a Exception exception because we are calling setter inside the setter.
Solution to this problem is to user backing fields. In Kotlin a backing field can be accessed using "field" keyword inside accessors. Take a look at corrected code snippet below.
class User{
var firstName : String get() = field
set(value) {
field = value
}
var lastName : String get() = field
set(value) {
field = value}
}
}
How it works , let's understand by an example , consider this
class Person {
var name: String = ""
}
If nothing is specified, the property(name) uses the default getter and setter. It can, of course,
be modified to run whatever custom behaviour you need, without having to change
the existing code:
So if want set custom behaviour to name property than we modify above class to this
class Person {
var name: String = ""
get() = field.toUpperCase()
set(value) {
field = "Name: $value"
}
}
If the property needs access to its own value in a custom getter or setter (as in this
case), it requires the creation of a backing field. It can be accessed by using field, a
reserved word, and will be automatically created by the compiler when it finds that
it’s being used.
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.