Firestore - how to exclude fields of data class objects in Kotlin - android

Firestore here explains, how I can use simple classes to directly use them with firestore: https://firebase.google.com/docs/firestore/manage-data/add-data
How can I mark a field as excluded?
data class Parent(var name: String? = null) {
// don't save this field directly
var questions: ArrayList<String> = ArrayList()
}

I realize this is super late, but I just stumbled upon this and thought I could provide an alternative syntax, hoping someone will find it helpful.
data class Parent(var name: String? = null) {
#get:Exclude
var questions: ArrayList<Child> = ArrayList()
}
One benefit to this is that, in my opinion, it reads a little clearer, but the main benefit is that it would allow excluding properties defined in the data class constructor as well:
data class Parent(
var name: String? = null,
#get:Exclude
var questions: ArrayList<Child> = ArrayList()
)

Since Kotlin creates implicit getters and setters for fields, you need to annotate the setter with #Exclude to tell Firestore not to use them. Kotlin's syntax for this is as follows:
data class Parent(var name: String? = null) {
// questions will not be serialized in either direction.
var questions: ArrayList<Child> = ArrayList()
#Exclude get
}

Related

Convert Firestore document to list of objects

I have this Firestore document Quiz_android that looks list this:
It is a simple array with maps in it. Now I would like to bind those results to some objects in Kotlin. Therefore I have made the following:
data class QuizBody(
val questions: List<Question>
)
data class Question(
val question: String,
val answers: List<String>,
val answer: Int
)
A Quizbody is just all the questions for the quiz in a list, and in that list, I have classes of Question which should be able to store all the data from the call.
But how do I bind the result from the call to those objects?
suspend fun getQuestions(quizToGet: String) {
try {
//firestore has support for coroutines via the extra dependency we've added :)
withTimeout(5_000) {
firestore.collection("Quizzes").document(quizToGet).get()
.addOnCompleteListener { task ->
if (task.isSuccessful) {
val result = task.result
if (result.exists()) {
val myObject = result.toObject(QuizBody::class.java)
println(myObject)
}
}
}
}
} catch (e: Exception) {
throw QuizRetrievalError("Retrieving a specific quiz was unsuccessful")
}
}
I have made this but this does not work.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.hva.madlevel7task2, PID: 3995
java.lang.RuntimeException: Could not deserialize object. Class com.hva.madlevel7task2.model.QuizBody does not define a no-argument constructor. If you are using ProGuard, make sure these constructors are not stripped
Edit:
I have updated the data class:
data class QuizBody(
var questions: List<Question>? = null
)
data class Question(
var question: String? = null,
var answers: List<String>? = null,
var answer: Int? = null
)
suspend fun getQuestions(quizToGet: String) it still the same, now I get this in the console:
I/QuizViewModel: function: getListQuestions
W/Firestore: (24.1.1) [CustomClassMapper]: No setter/field for Questions found on class com.hva.madlevel7task2.model.QuizBody (fields/setters are case sensitive!)
I/System.out: QuizBody(questions=null)
The following error:
java.lang.RuntimeException: Could not deserialize object. Class com.hva.madlevel7task2.model.QuizBody does not define a no-argument constructor.
Is very straightforward in my opinion. Your class "QuizBody" does not have a no-argument constructor. When you try to deserialize an object that comes from a Firestore database, the Android SDKs require that the class should mandatorily have a default no-argument constructor.
In Kotlin, the data classes don't provide a default no-arg constructor if all the properties of the class are declared with val. For such properties, Kotlin requires that their values be specified in the constructor since they can't possibly change later. So this is required because we need to ensure the compiler that all the properties have an initial value. You can provide to all of the properties an initial value of null or any other value you find more appropriate. So your classes should look like this:
data class QuizBody(
var questions: List<Question>? = null
👆 👆
)
data class Question(
var question: String? = null,
var answers: List<String>? = null,
var answer: Int? = null
)
Now adding the properties in the constructor, Kotlin will automatically generate a default no-argument constructor. In this way, the Firebase Android SDK will be able to use to generate setters for each property. If don't make this change, you won't be able to use automatic deserialization. You'll have to read the value for each property out of the DocumentSnapshot object and pass them all to Kotlin's constructor.
Edit:
if (task.isSuccessful) {
val document = task.result
if (document.exists()) {
val myObject = document.toObject(QuizBody::class.java)
println(myObject)
}
}

How to convert String array to the room entity object i.e DAO in kolin?

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

Exactly ONE TO ONE Relation in Room

I believe it have been asked several times but no working solution.
Room has #Relation annotation which is used for one to many relationships. That's basically if you have User and Pet models, as User can have several Pets, #Relation annotation works perfectly as return type of Pets are list (or set).
class UserAndAllPets : User() {
#Relation(parentColumn = "id", entityColumn = "userId")
var pets: List<Pet> = arrayListOf()
}
The problem is what if in my case User and Pet is one to one related. As in every user can have one pet. That means there is no point of using #Relation as it only supports list or set return types. And it's totally inefficient to use as a list, even if I use it. So I am looking for a exact one to one relation where I can get a result of
class UserAndPet {
var user: User? = null
var pet: Pet? = null
}
I have tried tried several was as well as this method (which has lots of upvotes, but it doesn't work).
Supposedly,
class UserAndPet {
#Embedded
var user: User? = null
#Embedded
var pet: Pet? = null
}
Should work, but I am getting
Not sure how to convert a Cursor to this method's return type (UserAndPet)
There is no conflict as I already use prefix for #Embedded fields.
And please, can you not redirect my to any other post on stack overflow, as I tried all but no luck.
Thanks
This feature has been added as of 2.2.0-alpha01 of room.
Ref - Room release 2.2.0-alpha01
When we considering 1 to 1 relationship below approach is also possible and for more please follow the link.
#Entity(tableName = "user")
class User(
val id: Int
// ...
) {
#Ignore /* Ignores the marked element from Room's processing logic. */
var pet: Pet? = null
}
#Entity(tableName = "pet")
class Pet(
val id: Int,
val userId: Int
)
/* Single task used for 1 to 1 read */
class UserWithPetReadTask : RxTask.CallableWithArgs() {
private var result = UserWithPetTaskResult()
override fun call(params: Array<out Any>?): Any {
App.mApp?.getDBLocked { appDb ->
/* Read user details from repo */
val user: User? = appDb?.getUserDao()?.getUserById("[userId]")
user.let {
/* Read pet associated and assign it to user*/
it?.pet = appDb?.getPetDao().getPetAssociated("[userId] or [user?.id]")
result.user = user
result.isSuccess = true
}
}
return result
}
}
class UserWithPetTaskResult {
var isSuccess = false
var user: User? = null
}

Room: related entities - usable public constructor

To get a OneToMany relation with Room I create a POJO with #Embedded object and #Relation variable.
data class SubjectView(
#Embedded
var subject: Subject,
#Relation(parentColumn = "idWeb", entityColumn = "subject_id", entity = Topic::class)
var topics: List<Topic>?
)
But while compiling I have this error
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)
[...]
Tried the following constructors but they failed to match:
SubjectView(biz.eventually.atpl.data.db.Subject,java.util.List<biz.eventually.atpl.data.db.Topic>) : [subject : subject, topics : null]
Well, that constructor [subject : subject, topics : null] looks like the good one ???
However, if I change my class with no-arg constructor and an all params constructor, it does work.
class SubjectView() {
#Embedded
var subject: Subject = Subject(-1, -1, "")
#Relation(parentColumn = "idWeb", entityColumn = "subject_id", entity = Topic::class)
var topics: List<Topic>? = null
constructor(subject: Subject, topics: List<Topic>?) : this() {
this.subject = subject
this.topics = topics
}
}
I would like to know why the first (quicker) version does not compile, as it is not as the documentation shows.
Default args for all variables (as I could have seen on other post) in a constructor (data) class seems not to be mandatory though?
Thanks
There are several topics how data class generate the constructors.
Since you have a nullable Object inside your constructor, it will generate all possible constructors. That means it generates
constructor(var subject: Subject)
constructor(var subject: Subject, var topics: List<Topic>)
There are two ways to solve that. The first one is to predefine all values like and create another ignored constructor with the desired constructor.
data class SubjectView(
#Embedded
var subject: Subject,
#Relation(parentColumn = "idWeb", entityColumn = "subject_id", entity = Topic::class)
var topics: List<Topic> = ArrayList()
) {
#Ignore constructor(var subject: Subject) : this(subject, ArrayList())
}
Another way is creating a half-filled data class like
data class SubjectView(#Embedded var subject: Subject) {
#Relation var topics: List<Topic> = ArrayList()
}
Take care that the first solution is the proper solution and you need to set #Ignore to any other constructor.

Room Database error with Kotlin Data 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.

Categories

Resources