Android data class structure for unlabelled API response - android

An API is returning responses without any outlying data fields, but instead only as an array object like this:
[{"name":"John Smith", "age":"44", "address":"1 Main Street, Anywhere, USA"}, {"name":"Jane Taylor", "age":"22", "address":"10 Suburbia Lane, Sometown, USA"},{"name":"Simon Jones", "age":"36", "address":"33 City Boulevard, Midvalley, USA"}]
My response data classes usually include labels, like this:
data class MyResponseClass (
val status: String? = null,
val description: String? = null
)
How should I structure a response class that does not include labels?
TIA.
UPDATE:
Following Ivo's answer (thank you, btw), how could such a class inherit a base class?
open class MyBaseReponseClass() : Serializable {
val status: String? = null
val description: String? = null
}
data class MyResponseClass (
val name: String? = null,
val age: String? = null,
val address: String? = null
) : MyBaseReponseClass()
The name, age, and address are in an array, but not status and description.
Thanks again!

Not sure what you mean with not using labels. it clearly has name, age and address so it will be
data class MyResponseClass (
val name: String? = null,
val age: String? = null
val address: String? = null
)
And where you indicate the type of the response you say it's a List<MyResponseClass>

Related

Kotlin Serialization #SerialName not working for Boolean

I'm using GSON to serialize some platform data. When I use #SerialName to capture platform data with a different naming convention in my app, it works for other types, but not Boolean types. As a simple example, if I have a class like...
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
#Serializable
data class Person (
#SerialName("first_name") val firstName: String? = null,
#SerialName("last_name") val lastName: String? = null,
val age: Int? = null
)
... everything works fine. The serializer finds first_name, last_name and age in the data and properly set the properties for the Person.
However, when I try to add a Boolean...
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
#Serializable
data class Person (
#SerialName("first_name") val firstName: String? = null,
#SerialName("last_name") val lastName: String? = null,
val age: Int? = null,
#SerialName("can_sing") val canSing: Boolean? = null
)
... the serializer does not catch and assign can_sing. It is strange that it works with a String but not a Boolean. Can any explain why I am seeing this behavior? I can work around this (for example, I can do val can_sing: Boolean? = null and it works), but I'm just wonder why #SerialName doesn't seem to work for a Boolean, or if I'm just missing something obvious.
You are mixing the Gson and Kotlin annotation types - Gson uses #SerializedName not #SerialName. I am not sure how your string types even work in that case (maybe something in how you call Gson that isn't included in the question).
As an example, the first class here (Person) can be serialized with the Kotlin serialization library, the second with Gson:
Kotlin annotations
#Serializable
data class Person (
#SerialName("first_name") val firstName: String? = null,
#SerialName("last_name") val lastName: String? = null,
val age: Int? = null,
#SerialName("can_sing") val canSing: Boolean? = null
)
Gson Annotations
data class PersonGson (
#SerializedName("first_name") val firstName: String? = null,
#SerializedName("last_name") val lastName: String? = null,
val age: Int? = null,
#SerializedName("can_sing") val canSing: Boolean? = null
)
Examples
Running this unit test with the Kotlin serialization library:
#Test
fun testJsonKotlin() {
val test = Person("hello", "world", 42, false)
val json = Json.encodeToString(test)
println(json)
val t2 = Json.decodeFromString<Person>(json)
println(t2)
}
produces the expected output:
{"first_name":"hello","last_name":"world","age":42,"can_sing":false}
Person(firstName=hello, lastName=world, age=42, canSing=false)
Doing that with Gson
#Test
fun testJsonGsonMixed() {
val testp = Person("hello", "world", 42, false)
val json = Gson().toJson(testp)
println(json)
val t2 = Gson().fromJson(json, Person::class.java)
println(t2)
}
technically works, but ignores the serialized name annotations (for all the cases, not just the boolean)
{"firstName":"hello","lastName":"world","age":42,"canSing":false}
Person(firstName=hello, lastName=world, age=42, canSing=false)
Using the Gson-annotated class with Gson
#Test
fun testJsonGson() {
val test = PersonGson("hello", "world", 42, false)
val json = Gson().toJson(test)
println(json)
val t2 = Gson().fromJson(json, PersonGson::class.java)
println(t2)
}
gives the correct response again
{"first_name":"hello","last_name":"world","age":42,"can_sing":false}
PersonGson(firstName=hello, lastName=world, age=42, canSing=false)

how to make an aotuGenerated field in a mutableList of parcelabel object in a feild of entity class Room kotlin

I have an entity class (PipeLine) that has a Mutable list of a parcelabel class (DamagePoint )
the main class PipeLine has a field val id:Int=0, primary key set to autoGerat=true it's working fine
the subclass DamagePoint also has a primary key val no:Int=1, I use it for points sequence it's not working!!
all the points generated has the no=0
Just to clarify, each PipeLine has a list of DamagePoints, sequenced by numbers
how would I do that !!
the class PipeLine.kt
#Parcelize
#Entity(tableName = "table_Lines")
data class PipeLine(
#PrimaryKey(autoGenerate = true)
val id:Int=0,
var name: String?,
var ogm: String?,
var length: String?,
var type: String?,
var i_start: String?,
var i_end: String?,
var start_point: String?,
var end_point: String?,
var work_date:String?,
var points: MutableList<DamagePoint>
):Parcelable
#Parcelize
data class DamagePoint(
#PrimaryKey(autoGenerate = true)
val no:Int=1,
var db: String? = null,
var depth: String? = null,
var current1: String? = null,
var current2: String? = null,
var gps_x: String? = null,
var gps_y: String? = null
):Parcelable
class DataConverter {
#TypeConverter
fun fromPoints(points: MutableList<DamagePoint>?): String? {
if (points == null) {
return null
}
val gson = Gson()
val type = object : TypeToken<MutableList<DamagePoint>?>() {}.type
return gson.toJson(points, type)
}
#TypeConverter
fun toPoints(points: String?): MutableList<DamagePoint>? {
if (points == null) {
return null
}
val gson = Gson()
val type = object : TypeToken<MutableList<DamagePoint>?>() {}.type
return gson.fromJson<MutableList<DamagePoint>>(points, type)
}
}
output :
id for PipeLine working and increasing
no for the DamagePoint not working and didn't increase
another output from emulator showing the point list int recyclerView
is the primaryKey don't work in the type converter
or there is another way to do it and make it increase?
please anything would help
I annotate it with #PrimaryKey and set autoGenerate to true
tried to switch between val and var , same thing
The problem is with the 1 integer which you set for the no. In this way, it does not work as auto-increment since there is a specified integer value (1) instead of 0.
So it means that if you set no to 0, it will work as unset and will increase the values.
val no:Int = 0
Also, you may need to change the no name to id.
the problem is I was trying to add a full list of DamagePoint every time I wanted to add a point to the points list in the PipeLine entity so I fetch the list from the object add new points to it and update the entire list for that pipeline object so it's normal to find all the point (no) equal to 1 because it's the default value for it
now I handle the no of each point I add to the list before updating it and adding it back to the pipeline object
I found a way to update only the points list in the pipeline object without updating the entire object
#Query("UPDATE table_Lines SET points=:points WHERE id=:id ")
fun updatePointsList(id:Int,points:MutableList)
if someone knows how to add an item to a mutable list which is a property of the entity class
without updating the list or updating the entity object
please add an answer

Create an AdapterFactory for gson for dynamic Types

I have a JSON string that I need to converted to data class object in Kotlin, the problem is that there is a field (details) that can have a different structure depending of the value of another field like this
val jsonString1 = "{'name': 'Juan', 'phase': 'step1', 'details': { 'name': 'product 1' }}"
val jsonString2 = "{'name': 'Juan', 'phase': 'step2', 'details': { 'position': 10 }}"
now I have something like
data class Customer(
var name: String? = null
var phase: String? = null
var details: Details? = null
)
data class Details(
var name: String? = null
)
data class Details2(
var position: Int? = null
)
now with gson I know I can
Gson().fromJson(jsonString1, Customer::class.java)
I want to be able to automatically use the right data class depending on the value of the phase field, I know I can create an adapterFactory, but I can't figure out how, an in kotlin is worse
I was reading this post
http://anakinfoxe.com/blog/2016/02/01/gson-typeadapter-and-typeadapterfactory/
and I'm pretty sure is the way to go, but I can't quite get it
Yep, it's pretty easy to write such adapter. I've slightly changed your example:
data class Customer(
var name: String? = null,
var phase: String? = null,
var details: Details? = null
)
sealed class Details {
data class Details1(var name: String? = null) : Details()
data class Details2(var position: Int? = null) : Details()
}
class CustomerDeserializer : JsonDeserializer<Customer> {
override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): Customer {
val customerObject = json.asJsonObject
val detailsObject = customerObject.getAsJsonObject("details")
val details = if (detailsObject.has("name")) {
Details.Details1(detailsObject.get("name").asString)
} else {
Details.Details2(detailsObject.get("position").asInt)
}
return Customer(
name = customerObject.get("name").asString,
phase = customerObject.get("phase").asString,
details = details
)
}
}
fun main() {
val gson = GsonBuilder()
.registerTypeAdapter(Customer::class.java, CustomerDeserializer())
.create()
println(gson.fromJson(jsonString1, Customer::class.java))
println(gson.fromJson(jsonString2, Customer::class.java))
}
data class Customer(
var name: String? = null
var phase: String? = null
var details: Details? = null
)
data class Details(
var name: String? = null
var position: Int? = null
)
Define Details class in this way
Gson().fromJson(jsonString1, Customer::class.java)
return a Customer either name is null or position is null

Customise single data class without creating multiple data classes in Kotlin

I have a scenario. I have created a data class in Kotlin like this:
data class AgentDetails(
val mobileNo: String,
val Name: String,
val Email: String,
val Password: String,
val Occupation: String,
val pincode: String,
val usertype: String,
val profilepic: String,
val AccountStatus: String
)
I want to send different type of objects of this data class to a web service:
1st object example:
val agentDetails = AgentDetails(mobileNo = mobileNumberText.text.toString(),
Name = userNameText.text.toString(),
Email = emailIdText.text.toString(),
Password = HashUtils.sha1(passwordText.text.toString()),
Occupation = item,
pincode = pinCodeText.text.toString(),
usertype = "Agent",
profilepic = "null", AccountStatus = "pending")
In 2nd object I only want to send mobile number. I dont wanna include any other field. Something like this:
val agentDetails = AgentDetails(mobileNo = mobileNumberText.text.toString())
And in 3rd object I only wanna send email id. Instead of creating multiple data classes. Can I use the same data class for multiple implementations?
Personally, I'd define three objects because they represent three different concepts (or projections of a concept). But if you make your properties nullable and provide a default value of null, you can get away with creating them as you want...
data class AgentDetails(
val mobileNo: String? = null,
val name: String? = null,
val email: String? = null,
val password: String? = null,
val occupation: String? = null,
val pincode: String? = null,
val usertype: String? = null,
val profilepic: String? = null,
val accountStatus: String? = null
)
Note: I've changed some of your property names to camelCase, as is the proper convention. And these all work fine:
AgentDetails(mobileNo = mobileNumberText.text.toString())
AgentDetails(email = "foo#example.com")
AgentDetails(name = "Foo", password = "Bar")
All of the other fields not provided will be null, and the types will be nullable, so you'll have to guard against that. Otherwise, I'd define three data classes for this.
Another solution would be to consider a sealed class structure:
sealed class AgentDetails
data class AgentByName(val name: String) : AgentDetails()
data class AgentByEmail(val email: String): AgentDetails()
// etc..
And then use it in a when expression:
fun doSomethingWithAgents(agentDetails: AgentDetails) {
when (agentDetails) {
is AgentByName -> // Do something
is AgentByEmail -> // Do Something
}
}
The easiest way is to make the fields nullable and provide default values:
data class AgentDetails(
val mobileNo: String? = null,
val Name: String? = null,
val Email: String? = null,
val Password: String? = null,
val Occupation: String? = null,
val pincode: String? = null,
val usertype: String? = null,
val profilepic: String? = null,
val AccountStatus: String? = null
)

Can I using one Model for two entities in Room persistence?

I would like to know that, is it possible to use one model for two entities while using Room Persistence in Kotlin?
For example, I have to create two pagers which are general page and star page.
Both of them have fields following..
id: String?, caption: String?, imageUrl:String?, isUserLike: Boolean
(And more)
Data came from two separate services and I want to save it separately.
How can I do this, or there have any ways to solve this problem?
Thank you
P.S. I'm the beginner of Kotlin and Room persistence.
You can create a separate abstract class that contains all those common fields
abstract class BaseModel(
#PrimaryKey
var id: String? = null,
var caption: String? = null,
var imageUrl: String? = null,
...
) {
...
}
then have that abstract class be extended on the other classes
#Entity(tableName = "a_model")
data class AModel (
mId: String? = null,
mCaption: String? = null,
mImageUrl: String? = null,
...
): BaseModel(id = mId,
caption = mCaption,
imageUrl = mImageUrl,
...) {
...
}
you can also add other fields that are exclusive to that entity
#Entity(tableName = "b_model")
data class BModel (
mId: String? = null,
mCaption: String? = null,
mImageUrl: String? = null,
...
var nonCommonField: String? = null, // like this
): BaseModel(id = mId,
caption = mCaption,
imageUrl = mImageUrl,
...) {
...
}
Android Room will require fields to have a default value for each field or an empty constructor, if I remember correctly.

Categories

Resources