Moshi Retrofit Request - kotlin

I'm doing a POST request and I'm using
#JsonClass(generateAdapter = true)
And the request body is as follow:
{
"request": "some data",
"data": {}
}
When creating my data class, how could I declare the empty object "data" = {}
My data class is as follow:
#JsonClass(generateAdapter = true)
data class Request(val request: String, val data: ?)
But, how could I create the empty body?

Your data should be nullable type of necessary class with 'null' default value, so like:
#JsonClass(generateAdapter = true)
data class Request(val request: String, val data: MyData? = null)
I think it will works.

Related

How to make multiple constructor generic data Class in kotlin

I want to use multple Constructors like this :
data class MyData<T>(
val code: String,
val message: String,
val data: T? = null,
) {
constructor(code: String, message: String) : this(code, message, null)
}
and I Use
MyData(code = "-1", message = "Support only application/json Content-Type") // Error : Not Enough Information To Infer Parameter T
MyData(code = "-1", message = "message", data = "data")
How to Use MyData property 'data' default value is 'null'?
( I want to MyData(code, message) not MyData(code, message) )
You pass the type like this:
val obj1 = MyData<String>(code = "-1", message = "Support only application/json Content-Type")
Note that data should probably be a var instead of a val, or you won't be able to change it.

Generic Base Respose models in Kotlin

Everyone following is my json response:
{
"requestResponse": {
"status": 1,
"result": true,
"msg": "Success"
},
"userId": 5504
}
And following is my Base Response class:
class BaseResponses<T>{
lateinit var requestResponse: RequestResponse
}
and following are my User data class parameters.
data class User(val userId:Int)
And below as implementation:
#POST(ApiUrls.CREATE_USER)
fun createUser(#Body body: CreateUser): Single<BaseResponses<User>>
my question is that how can I access T type which is User in the Base class would highly appreciate the help.
Thanks
You don't need a genetic type - you need to inherit the properties.
data class BaseResponses { // Remove T, it's doing nothing
lateinit var requestResponse: RequestResponse
}
// Extend base class to inherit common `requestResponse` field
data class User(val userId:Int) : BaseResponses()
// User now will contain requestResponse object
#POST(ApiUrls.CREATE_USER)
fun createUser(#Body body: CreateUser): Single<User>
I might be understanding you wrong, you just want to re-use the RequestResponse class since it is generic and will be common in all your APIs. So just have it as a parameter in User data class.
So it will be like this
data class User(
val requestResponse: RequestResponse,
val userId: Int
)
Now you can simply access it directly from User object. You can even go a step further and assign it default values like this
data class User(
val requestResponse: RequestResponse = RequestResponse(),
val userId: Int = 0
)
data class RequestResponse(
val msg: String = "",
val result: Boolean = false,
val status: Int = 0
)

Kotlinx Serialization MissingFieldException

I am in the process of converting from Moshi to kotlinx serialization with Ktor and when I try to make a request to get data I am getting this error
kotlinx.serialization.MissingFieldException: Field 'attachments' is
required, but it was missing
which makes sense since this specific response does not contain this field
Response Json
{
"data": {
"id": "1299418846990921728",
"text": "This is a test"
}
}
BUT my Serialized class has the attachments field as nullable (it is in the response only when it needs to be) so it should just ignore it I thought like it did with Moshi
#Serializable
data class ResponseData(
val id: Long
val attachments: Attachments?,
val author_id: String?,
val text: String
}
In my Ktor client setup I have it set to ignore unknown keys
private val _client: HttpClient = HttpClient(engine) {
install(JsonFeature) {
val json = Json {
this.isLenient = true
this.ignoreUnknownKeys = true
}
serializer = KotlinxSerializer(json)
}
}
Why is it still saying that the field is required even though its nullable?
I figured it out, apparently even though you mark something as nullable its still considered required.
For it to truly be optional you need to give it a default value so for example the data class would look like this with the nullables
#Serializable
data class ResponseData(
val id: Long
val attachments: Attachments? = null,
val author_id: String? = null,
val text: String
}
once you set the value the fields becomes optional and wont throw that exception
As of v1.3.0, you can configure the Json feature to treat absent fields as null, with explicitNulls = false
install(JsonFeature) {
serializer = KotlinxSerializer(
json = kotlinx.serialization.json.Json {
explicitNulls = false
}
)
}
The documentation for explicitNulls:
Specifies whether null values should be encoded for nullable properties and must be present in JSON object during decoding.
When this flag is disabled properties with null values without default are not encoded; during decoding, the absence of a field value is treated as null for nullable properties without a default value.
true by default.
I had the same exception with the Spring RestController, where incase of missing data, I need to send BAD_REQUEST response. I could achieve this with the following code.
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ExceptionHandler(value = [SerializationException::class])
open fun handleRequestError(ex: SerializationException) = //
log.warn("Unable to process incomplete request", ex)

Exclude non-null properties when serializing a Kotlin data class

I often will create a data class in Kotlin that is used internally for data models. Example:
data class MyDataModel(
var id: String? = null,
var ownerId: String,
var name: String,
var isPrivate: Boolean = false,
)
I often need to serialize these classes to JSON. The problem is that some of the class properties are not nullable and there are cases where I need to exclude those fields in the serialization. I haven't found a clean and simple way to do that. The solution I currently use is not to use non-nullable properties and then set those that I don't want serialized to null.
Is there another approach?
Solution using kotlinx.serialization:
Define class, including all fields you want to be serialized, mark it as #Serializable
#Serializable
open class MyDataModelSerializable(
open var id: String? = null
)
Make your data class to be its subtype:
data class MyDataModel(
var ownerId: String,
var name: String,
var isPrivate: Boolean = false,
override var id: String? = null
) : MyDataModelSerializable(id)
Serialize instances of MyDataModel class with serializer for MyDataModelSerializable:
val s = serializer<MyDataModelSerializable>()
println(Json.encodeToString(s, MyDataModel(ownerId = "1", name = "2", id = "3", isPrivate = true))) //{"id":"3"}
println(Json.encodeToString(s, MyDataModel(ownerId = "1", name = "2", isPrivate = true))) //{}
println(Json{encodeDefaults = true}.encodeToString(s, MyDataModel(ownerId = "1", name = "2"))) //{"id":null}

Firebase Firestore toObject() with Kotlin

I try to use Firebase Firestore in a Kotlin project. Everything is going fine except when I want to instantiate an object with DocumentSnapshot.toObject(Class valueType).
Here is the code :
FirebaseFirestore
.getInstance()
.collection("myObjects")
.addSnapshotListener(this,
{ querySnapshot: QuerySnapshot?, e: FirebaseFirestoreException? ->
for (document in querySnapshot.documents) {
val myObject = document.toObject(MyObject::class.java)
Log.e(TAG,document.data.get("foo")) // Print : "foo"
Log.e(TAG, myObject.foo) // Print : ""
}
}
})
As you can see, when I use documentChange.document.toObject(MyObject::class.java), my object is instantiated but the inner fields are not set.
I know that Firestore needs the model to have an empty constructor. So here is the model :
class MyObject {
var foo: String = ""
constructor(){}
}
Can somebody tell me what I'm doing wrong?
Thank you
You forgot to include the public constructor with arguments, or you can also just use a data class with default values, it should be enough:
data class MyObject(var foo: String = "")
In my case I was getting a NullPointerException because there wasn't a default constructor.
Using a data class with default values fixed the error.
data class Message(
val messageId : String = "",
val userId : String = "",
val userName : String = "",
val text : String = "",
val imageUrl : String? = null,
val date : String = ""
)
class MyObject {
lateinit var foo: String
constructor(foo:String) {
this.foo = foo
}
constructor()
}