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.
Related
I have an app which is mixed Java and Kotlin.
In the Kotlin code I use Moshi to convert an object to Json in a convertor for a Room database table.
I have one case that works perfectly but another one produces the error:
Not enough information to infer type variable T
This is what my code looks like:
val type: Type = Types.newParameterizedType(
MutableMap::class.java,
LayerTwoConn::class.java,
TWeFiState::class.java,
WfMeasureFileMgr::class.java,
Traffic::class.java,
ThroughputCalculator::class.java,
CellSubTechThroughput::class.java,
LongValuesAverageCalculator::class.java,
LayerTwoConn.SenselessTraffic::class.java
)
val json = Moshi.Builder().build().adapter(type).toJson(layerTwoConn)
I have included all the classes that are used in the objects.
What have I missed?
This case works perfectly:
val type: Type = Types.newParameterizedType(
MutableList::class.java,
CnrScan::class.java,
)
val jsonAdapter: JsonAdapter<List<CnrScan>> = Moshi.Builder().build().adapter(type)
val json = jsonAdapter.toJson(list)
In this object, all the internally used classes are standard Java class and not my own.
Have I missed something simple?
I don't know if this is important but the class LayerTwoConn's constructor is private.
I think you are trying to convert too many classes into one type, try to convert MutableMap class and LayerTwoConn class.
Do note that Room uses SQL architecture, so try to predict what you want your table to contain
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 running into some unknown error. This breaks my assumption of null-safety with Kotlin data class and Api responses.
Say, I have a data class say Person:
data class Person(val name: String) {
constructor() : this("")
}
This will generate an object Person with default name value i.e. non-null.
Earlier, When I use a default retrofit client with GsonConverterFactory.create() (added as a converter factory). In default mode, Gson doesn't serialize a null value. But today I found out that field is getting serialized to null.
I verfiy the same in ReflectiveTypeAdapterFactory https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java#L206
Here the instance value is having non-null field but after reading each field (field.read(in, instance);) it is assigning the null value.
I am expecting the null values to be skipped during serialization or is it deserialization?
Edit:
Looks like it is deserializing nulls not serializing null problem.
Reference: https://github.com/google/gson/issues/1148
Let me know if any detail missing or creating confusion.
You have to make name parameter nullable by changing type;
String
to
String?
I am currently integrating Firestore documents within a Kotlin application using an annotated POJO to pull the data in. As part of this I also need the ID of the document.
I'm trying to do this using the new #DocumentId notation described here...
However the annotation isn't being recognised in my POJO class (unresolved reference: DocumentId)
In my gradle file I have...
implementation 'com.google.firebase:firebase-firestore:21.2.1'
In my model class I have...
data class DocumentModel(
#DocumentId var id: String
#PropertyName var field1: String
...etc...
)
The other annotations work fine, it's just the DocumentId one which isn't working. Anyone have any ideas on what I'm doing wrong?
According to official document (https://firebase.google.com/docs/reference/android/com/google/firebase/firestore/DocumentId.html),
This annotation is applied to a property that is not writable (for example, a Java Bean getter without a backing field).
So, you have to use val instead of var.
data class DocumentModel(
#DocumentId val id: String
...etc...
)
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