I have simple data classes. I mean, they are data classes logically, but not data class, because I need inheritance and other constructors. They only have fields (of basic types Int?, String?, or List<String>?, etc), and constructors.
I need to pass them (all of their fields need to be passed) from Activity to Activity, so I need to make them parcellisable (or is there a better way?). I first created them as data class and just added #Parcelize. Even though there was a warning and red line that said "CREATOR" or something, I could ignore them and the app worked as intended.
But, now for the reasons above, I changed them to normal classes, and suddenly there is a compilation error.
Error:java.util.NoSuchElementException: Collection contains no element matching the predicate.
at org.jetbrains.kotlin.android.parcel.ParcelableCodegenExtension.getPropertiesToSerialize(ParcelableCodegenExtension.kt:374)
....too long...
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Error:Execution failed for task ':app:kaptGenerateStubsDebugKotlin'. Internal compiler error. See log for more details
How can I solve this problem? Should I use data class? But I need to parse a JSON object to create them. The classes look like these (not actual classes, but simplified for illustration purposes). Is there a better way than implementing that boring, bulky parcellable code by hand?
#Parcelize
open class Dog():Parcelable
{
var someField1;
var someField2;
constructor(data:JSON):this()
{
//parse data to set the fields.
}
}
#Parcelize
class Doge:Dog
{
var someField3;
var someField4;
constructor():super(); //I do not use it, but for parcellable
constructor(data:JSON):super(data)
{
//parse data to set the fields.
}
}
PS. I had to switch to PaperParcel. It was very similar to Kotlin's, but it did not require a primary constructor. It only required the same thing to be any constructor, so I could just create a secondary constructor with the same argument names as those of fields, and it worked. Although, I wonder why the CREATOR could not be created automatically.
For example,
#PaperParcel
class Doge:Dog
{
var someField3;
var someField4;
//Needed only for PaperParcel
constructor(someField3, someField4)
{
this.someField3 = someField3;
this.someField4 = someField4;
}
companion object
{
#JvmField val CREATOR = PaperParcelDoge.CREATOR
}
//end of PaperParcel stuff.
constructor(data:JSON):super(data)
{
//parse data to set the fields.
}
}
As stated here, your properties should be declared inside your primary constructor.
Parcelable support
Android Extensions plugin now includes an automatic
Parcelable implementation generator. Declare the serialized properties
in a primary constructor and add a #Parcelize annotation, and
writeToParcel()/createFromParcel() methods will be created
automatically:
#Parcelize
class User(val firstName: String, val lastName: String) : Parcelable
Related
I'm trying to use a room entity with a value class:
#JvmInline
value class UserToken(val token: String)
and the entity:
#Entity(tableName = TABLE_AUTH_TOKEN)
data class TokenEntity(
#PrimaryKey val id: Int = 0,
val token: UserToken
)
I get the following 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).
public final class TokenEntity {
^
is it even possible to use room with value class? I couldn't find anything about this. thanks
See the comment from #CommonsWare. Android does not yet support value classes for Room.
The same holds true for the value classes introduced in kotlin 1.5. The type is not supported.
— Support Inline class in Room entity
Here is a possible explanation according to Kotlin Inline Classes in an Android World.
Looking to solve this you could try and add a TypeConverter for your Inline class, but since your Inline class is just the value it wraps when it’s compiled, this doesn’t make much sense and it doesn’t work as you’d expect even if you tried...
I’m just guessing it’s because this is a TypeConverter converting UserId to Int which is basically the same as Int to Int 😭. Someone will probably solve this problem, but if you have to create a TypeConverter for your Inline class then you are still plus one class for the count (multidex). 👎
I think yes if you can provide a type converter for it to change it to some sort of primitive data type (int , string, long ...etc) when it needs to be stored, and to change it back to its class type when it's fetched from database.
You can read about Type Converters from here
Referencing complex data using Room
other than that, your other class should be an entity and bind both your entities together using a Relation.
at least that's what I know about how to use Room.
UserToken always will have only one attribute? In this case, you don't need two classes, just use token: String directly on your entity class;
If you really need keep this class, you have two options:
TypeConverter, where you basically will convert the object into a json, and save as string in the database;
Relation, where you will transform the UserToken in a entity, and on TokenEntity save the tokenId.
I am currently creating a library for private use which provides an Annotation (lets call it #Something) which can be used on Properties of a data class. I created the annotation like so:
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.PROPERTY)
annotation class Something
It will be used like this
data class SomeDataClass (val param1: String, #Something val param2: String, val param3: String)
I then want to be able to access the following things on the class that contains properties with this annotation:
memberFunctions (to get the copy constructor)
parameters of the copy constructor
memberProperties
property annotations
property name
List item
Now everything works properly until I minify my library with ProGuard. As I understand it, I need to describe my ProGuard rules in consumer-rules.pro. But what exactly do I need to add here? I want every data class in the application that uses my library that has a property annotated with #Something to be kept so I can read the things above from it.
I am having trouble in understanding the way #Parcelize working in Kotlin. According to documentations
only the primary constructor properties will be serialized.
But when I serialize and deserialize classes with empty primary contractors, it is still serializing and deserializing all the fields. For example, below class
#Parcelize
class Node(): Parcelable {
var field: String? = null
}
As primary contractor doesn't have any field, according to documentations I should have field = null always after ser/des. But whenever I run below codes
val before = Node()
before.field = "someField"
val bundle = Bundle().apply{ putParcelable("someKey", before) }
val after = bundle.getParcelable<Node>("someKey")
field is successfully serialized and deserialized and will have value of someField.
Am I missing something or did Parcelize got updated but they didn't update documentation?
By the way if I leave Node declaration as above, Android Studio gives me warning that field will not be serialized into Parcel. But it is.
You should define it in the constructor itself and it will work just fine.
#Parcelize
class Node( var field: String? = null) : Parcelable
And to use empty constructors in kotlin you can add this in app gradle file.
apply plugin: 'kotlin-noarg'
With this you can use classes with empty constructors.
I hope this helps.
Data Model
data class AuthDataModel #Inject constructor(
var username: String = "",
var password: String = "",
var mobileData: String = "
Explanation
I am trying to inject authentication data model to authentication view model in kotlin, but it does not compile with message("Types may only contain one #Inject constructor)
Moving my comment to an answer:
If you have a constructor with default arguments, Kotlin actually generates additional constructors. In your case, you have a 3 arg constructor where all are optional, which generates a total of 4 constructors. Kotlin apparently associates any annotations on the primary constructor with all the generated ones as well, which means you ended up with 4 #Inject constructors.
You have two options:
The first, as you mentioned yourself, remove all the default values. If there are no default values, only one constructor is generated with the annotation.
Alternatively, you can also create additional constructors yourself and point it to the primary. This would also let you manually specify only one to have the #Inject annotation, while the others don't. Basically:
data class AuthDataModel #Inject constructor(
var username: String,
var password: String,
var mobileData: String) {
constructor(username: String) : this(username, "", "") {}
constructor(username: String, password: String) : this(username, password, "") {}
}
Not using default values prevents multiple #Inject constructors from being generated, and the secondary constructors should1 keep everything functioning as expected. This is basically overloading the constructor, and it's equivalent to what you'd do in Java when certain variables are optional. Should therefore be fine.
1: I haven't done Android in a while, and I've never used #Inject. If option 2 doesn't work (as in #Inject doesn't allow it, or doesn't work as expected, etc.), that only leaves option 1, and requires every parameter to be explicitly passed. The secondary constructors calling the primary constructor should be enough to keep everything working, though.
I use kotlinx.serialization on Kotlin native project, I a defined Super class for my models and all of the models extends from it.
I defined a function to called toJSON() for serialize variables and fields inside model that all of class models have it.
#Serializable
open class Model {
fun toJSON(): String = JSON.stringify(this);
}
And I created a subclass
class Me : Model() {
var name:String = "Jack";
}
but when I invoke JSON.stringify(this), IDE get a Warning to me:
This declaration is experimental and its usage must be marked with '#kotlinx.serialization.ImplicitReflectionSerializer' or '#UseExperimental(kotlinx.serialization.ImplicitReflectionSerializer::class)'
I paid attention and I used #ImplicitReflectionSerializer annotation while not worked.
Where is my problem?
This is discussed here. It's the particular overload you're using which is still experimental. So your options are either to use the other overload (which takes in a serializer) or to use one of the annotations mentioned in the error message. If you look at the answer to the question I linked (and the comments following it), you'll see it talks about using #UseExperimental and where it should be used.