I'm using retrofit and kotlin for my andorid app.
For one of my APIs, I have the following data classes
class Set(val set_id: Long, val tickets: List<Ticket>, val timer: Int) {}
class Ticket(val ticket_id: Long, val rows: List<Row>) {}
class Row(val row_id: Long, val row_numbers: List<Int>) {}
Sample JSON DATA
{
"set_id": 60942,
"tickets": [
{
"ticket_id": 304706,
"rows": [
{
"row_id": 914116,
"row_numbers": [
0,
11,
21,
0,
42,
52,
0,
76,
85
]
}
]
}
],
"timer": 12
}
Set class contains a list of Ticket and each ticket has a list of Row
My JSON object contains only these values and it is working fine till here. Retrofit mapping is also working.
Problem:
I want to add my own a boolean field/variable isPlaying for class Ticket, which will be updated in the app later. But, this field should be set to true by default.
So I've tried this
class Ticket(val ticket_id: Long, val rows: List<Row>, var isPlaying: Boolean = true) {}
and this
class Ticket(val ticket_id: Long, val rows: List<Row>) {
var isPlaying: Boolean = true
}
NOTE: JSON doesn't have isPlaying key, I want it for app logic only.
Both did not work, isPlaying is always showing false. I want it to be true by default.
Please, help me out. Thank you!
Default arguments would not work in data class in the case when objects are instantiated with retrofit due to parsing library used underneath, which probably creates objects without calling constructor. For example Gson uses sun.misc.Unsafeto create objects.
What you can do - is to add backing property for fields that have default values:
class Ticket(
val ticket_id: Long,
val rows: List<Row>,
private var _isPlaying: Boolean? = true
) {
var isPlaying
get() = _isPlaying ?: true
set(value) {
_isPlaying = value
}
}
The root of the problem is the fact that GSON creates object not via constructor call, but via Unsafe staff. Thus it leads to broken things like null-safety or property initialization.
The only way I know how to workaround your case looks like this:
class Ticket(val ticket_id: Long, val rows: List<Row>) {
private var _isPlaying: Boolean? = null
var isPlaying: Boolean
get() = _isPlaying ?: true // <-- default value
set(value) {
_isPlaying = value
}
}
Here we create one backing property _isPlaying which will be initialized to null by GSON. Next we add custom getter and setter for public property which checks each it is invoked if _isPlaying is null. And if so it returns your default value. Otherwise, it returns the value stored in _isPlaying
Yes, it looks ugly. Otherwise you should consider using another json library like Jackson which as I know supports Kotlin well.
With using GsonBuilder setFeildNamePolicy
val gson = GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()
val info = gson.fromJson(json, Account::class.java)
data class Account(
#SerializedName("user_name") var userName: String = "",
#SerializedName("ticket_id") var ticketId: String = "",
#SerializedName("row_id") var rowId: String = "")
}
Related
I am trying to figure out how to loop a data class. I have a function called getAges() which contains a listof Ages from 1 - 10. Each age are called from a data class called Age, which should be an Int. How can I successfully loop through Age with different numbers, for ex 1-10? Appreciate the feedback!
My Data class:
#Entity(tableName = "dropdown_age")
data class Age(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "age")
val age: Int?
)
My Function called getAges:
class ProfileViewModel: Viewmodel() {
fun getAges() = listOf(
Age(1), Age(2), Age(3), Age(4), Age(5),
Age(6), Age(7), Age(8), Age(9), Age(10),
)
}
There is a List "constructor" function that can be used to create a List using a lambda where the lambda parameter is an index, starting at 0. (I use quotation marks for constructor because interfaces don't have true constructors. This is just a function that looks like a constructor because of how it is capitalized.)
fun getAges() = List(10) { Age(it + 1) }
Or you can use the map function with a range. map modifies each item out of any Iterable to produce a new List.
fun getAges() = (1..10).map { Age(it) }
// or
fun getAges() = (1..10).map(::Age)
Using collections api forEach
getAges().forEach{
println(it.age)
}
Or using normal For loop
for (age: Age in getAges()) {
println(age.age)
}
https://github.com/neuberfran/JThings/blob/main/app/src/main/java/neuberfran/com/jfran/model/FireFran.kt
I have this POJO above with the error mentioned in the topic. I know it is a mistake already mentioned here, but I have tried several classes (besides this one) and I have not been successful, since my model/POJO class (and Code implementation) is different from several that I saw:(Every help is welcome)
Could not deserialize object. Class does not define a no-argument
constructor. If you are using ProGuard, make sure these constructors
are not stripped (found in field 'value')
Change made to the garagem document, exchanged value for valorb, etc...
The error is very clear, your class "FireFran" doesn't have a no-argument constructor. When you try to deserialize an object from Cloud Firestore, the Android SDKs require that the class must have a default no-arg constructor and also setters that map to each database property.
In Kotlin, the data classes don't provide a default no-arg constructor. So you need somehow 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 "FireFran" might look like this:
class FireFran(
var alarmstate: Boolean = false,
var garagestate: Boolean = false,
var id: String? = null,
var userId: String? = null,
var value: FireFranValue? = null //Newly added
) {
//var value: FireFranValue = FireFranValue(false, 0)
companion object Factory {
fun create() :FireViewModel = FireViewModel()
var COLLECTION = "device-configs"
var DOCUMENT = "alarme"
var FIELD_userId = "userId"
}
}
Now adding the properties in the constructor, Kotlin will automatically generate a default no-arg constructor. In this way, the Firebase Android SDK will be able to use. It will also generate setters for each property. Please see that each property is var and not a val, and provides a default null value in case of "id" and "userId".
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:
In your screenshot, the "value" property is on an object of type "FireFranValue", which has only two properties, "brightness" and "on". To be able to read the data under "value", your "FireFran" class should contain a new property of type "FireFranValue". Please check above the class.
If you don't want to use automatic deserialization, you can get the value of each property individually. For example, you can get the value of the "userId" property using DocumentSnapshot's getString(String field) method:
val userId = snapshot.getString("userId")
Edit2:
Your three classes should look like this:
class FireFran(
var alarmstate: Boolean = false,
var garagestate: Boolean = false,
var id: String? = null,
var userId: String? = null,
var owner: String? = null,
var value: FireFranValue = FireFranValue(false),
var valorb: FireFranValueB = FireFranValueB(openPercent = 0)
)
data class FireFranValue(
var on: Boolean // = false
)
data class FireFranValueB(
var openPercent: Number // = 0
)
Edit3:
class FireFran(
var alarmstate: Boolean = false,
var garagestate: Boolean = false,
var id: String? = null,
var userId: String? = null,
var owner: String? = null,
var value: FireFranValue? = null,
var valorb: FireFranValueB? = null
)
data class FireFranValue(
var on: Boolean? = null
)
data class FireFranValueB(
var openPercent: Number? = null
)
Now, "on" and "openPercent" will get the values from the database.
Please check the following class declaration:
You can define a no-arg constructor in kotlin like this:
data class SampleClass(
val field1: String,
val field2: String
) {
constructor(): this("", "")
}
First thing: It was not necessary to create the field (map type) called valorb. It was resolved with value.openPercent
As I have two documents (alarme and garagem) I created two POJO classes (FireFran and FireFranB)
https://github.com/neuberfran/JThingsFinal/blob/main/app/src/main/java/neuberfran/com/jfran/model/FireFranB.kt
The secret was to treat the value.on and value.openPercent fields as maps (as facts are):
https://github.com/neuberfran/JThings/blob/main/app/src/main/java/neuberfran/com/jfran/model/FireFran.kt
I have this POJO above with the error mentioned in the topic. I know it is a mistake already mentioned here, but I have tried several classes (besides this one) and I have not been successful, since my model/POJO class (and Code implementation) is different from several that I saw:(Every help is welcome)
Could not deserialize object. Class does not define a no-argument
constructor. If you are using ProGuard, make sure these constructors
are not stripped (found in field 'value')
Change made to the garagem document, exchanged value for valorb, etc...
The error is very clear, your class "FireFran" doesn't have a no-argument constructor. When you try to deserialize an object from Cloud Firestore, the Android SDKs require that the class must have a default no-arg constructor and also setters that map to each database property.
In Kotlin, the data classes don't provide a default no-arg constructor. So you need somehow 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 "FireFran" might look like this:
class FireFran(
var alarmstate: Boolean = false,
var garagestate: Boolean = false,
var id: String? = null,
var userId: String? = null,
var value: FireFranValue? = null //Newly added
) {
//var value: FireFranValue = FireFranValue(false, 0)
companion object Factory {
fun create() :FireViewModel = FireViewModel()
var COLLECTION = "device-configs"
var DOCUMENT = "alarme"
var FIELD_userId = "userId"
}
}
Now adding the properties in the constructor, Kotlin will automatically generate a default no-arg constructor. In this way, the Firebase Android SDK will be able to use. It will also generate setters for each property. Please see that each property is var and not a val, and provides a default null value in case of "id" and "userId".
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:
In your screenshot, the "value" property is on an object of type "FireFranValue", which has only two properties, "brightness" and "on". To be able to read the data under "value", your "FireFran" class should contain a new property of type "FireFranValue". Please check above the class.
If you don't want to use automatic deserialization, you can get the value of each property individually. For example, you can get the value of the "userId" property using DocumentSnapshot's getString(String field) method:
val userId = snapshot.getString("userId")
Edit2:
Your three classes should look like this:
class FireFran(
var alarmstate: Boolean = false,
var garagestate: Boolean = false,
var id: String? = null,
var userId: String? = null,
var owner: String? = null,
var value: FireFranValue = FireFranValue(false),
var valorb: FireFranValueB = FireFranValueB(openPercent = 0)
)
data class FireFranValue(
var on: Boolean // = false
)
data class FireFranValueB(
var openPercent: Number // = 0
)
Edit3:
class FireFran(
var alarmstate: Boolean = false,
var garagestate: Boolean = false,
var id: String? = null,
var userId: String? = null,
var owner: String? = null,
var value: FireFranValue? = null,
var valorb: FireFranValueB? = null
)
data class FireFranValue(
var on: Boolean? = null
)
data class FireFranValueB(
var openPercent: Number? = null
)
Now, "on" and "openPercent" will get the values from the database.
Please check the following class declaration:
You can define a no-arg constructor in kotlin like this:
data class SampleClass(
val field1: String,
val field2: String
) {
constructor(): this("", "")
}
First thing: It was not necessary to create the field (map type) called valorb. It was resolved with value.openPercent
As I have two documents (alarme and garagem) I created two POJO classes (FireFran and FireFranB)
https://github.com/neuberfran/JThingsFinal/blob/main/app/src/main/java/neuberfran/com/jfran/model/FireFranB.kt
The secret was to treat the value.on and value.openPercent fields as maps (as facts are):
I have a POJO model object "apiResponse" that has values from a previous API call.
{
"status":200,
"userModel":{
...SOME VARIABLES...
},
"otherContentRelatedToUserModel":{
..SOME RELATED CONTENT..
}
}
This apiResponse has an "UserModel" as an inner object.
What i want to do is pass this "apiResponse" object to another api call whose response is "UserModel", and have it update only the UserModel object in the APIResponse POJO object.
The objective is to keep a single source of related content, which could change based on the interaction in the application, but might not update the rest of the related content.
Or is it possible to atleast update an already created pojo model as a whole, updating the variable values in the model.?
Reason for this ::
The API's content does not change for a set amount of time in the server, mainly to avoid over traffic to the server. So some amount of logic has to be implemented on the application side. Currently using a DB is not really a viable option.
Basically update only a portion of the already created POJO class object with another api call.
Is this possible in android(kotlin) using retrofit? Or is there any other way this could be achievable?
I think it is not possible to populate the additional fields in the existing UserModel object by using Retrofit, but you can do some magic with GSON:
data class UserModel(
val userId: Int? = null,
val userName: String? = null)
class OtherContentRelatedToUserModel
data class ApiResponsePojo(
val status: Int? = null,
val userModel: UserModel? = null,
val otherContentRelatedToUserModel: OtherContentRelatedToUserModel? = null)
class UserModelInstanceCreator(var userModelToUpdate: UserModel? = null)
: InstanceCreator<UserModel> {
override fun createInstance(type: Type?): UserModel {
return userModelToUpdate ?: UserModel()
}
}
val apiResponseJson =
"""
{
"status":200,
"userModel":{
"userId": 1
},
"otherContentRelatedToUserModel":{
}
}
"""
val userModelResponseJson =
"""
{
"userName": "john wick"
}
"""
val userModelInstanceCreator = UserModelInstanceCreator()
val gson = GsonBuilder()
.registerTypeAdapter(UserModel::class.java, userModelInstanceCreator)
.create()
val apiResponse: ApiResponsePojo = gson.fromJson(apiResponseJson, ApiResponsePojo::class.java)
userModelInstanceCreator.userModelToUpdate = apiResponse.userModel
gson.fromJson(userModelResponseJson, UserModel::class.java)
...
// apiResponse.toString() result
// ApiResponsePojo(status=200, userModel=UserModel(userId=1, userName=john wick)...
I am given a Json data where an image and some other data is stored. I am trying to fetch that image in an imageview using Retrofit. I created a DTO of the required things as detailed in the code. I am getting and error, on retrofit failure. How can I solve this?
Created Retrofit Instance
Created DTO of JSON data and properties
API service created also
https://s3.ap-south-1.amazonaws.com/zestlife/promotional_banner.json
Link where JSON data is stored.
#Parcelize
#JsonIgnoreProperties(ignoreUnknown = true)
open class MerchantPromotionDTO(
#JsonProperty("image") var image: ImageUrlsDTO? = null,
#JsonProperty("cta") var cta: CTADTO? = null,
#JsonProperty("probability") var probability: Int? = 0,
#JsonProperty("isDismissible") var isDismissible: Boolean? = true,
#JsonProperty("showImmediate") var showImmediate: Boolean? = false
) : BaseResponseDTO()
#Parcelize
#JsonIgnoreProperties(ignoreUnknown = true)
class MerchantpromotionBDTO(
#JsonProperty("promotions") var promotions: ArrayList<MerchantPromotionDTO>?=null
) : BaseResponseDTO()
#GET("https://demo6861386.mockable.io/banner/test")
fun getPromotionalBanner(): Call<ArrayList<MerchantpromotionBDTO>>
fun getPromotionalDetails(): LiveData<ResponseDTO<ArrayList<MerchantpromotionBDTO>>>{
val pBannerDetails=MutableLiveData<ResponseDTO<ArrayList<MerchantpromotionBDTO>>>()
ApiComponent.enqueue({
getPromotionalBanner(
)
},object :OnRequestComplete<ArrayList<MerchantpromotionBDTO>>{
override fun onComplete(responseDTO: ResponseDTO<ArrayList<MerchantpromotionBDTO>>) {
pBannerDetails.value=responseDTO
}
}
)
EDIT:
override fun onStart() {
super.onStart()
populateData()
}
#Synchronized
private fun populateData() {
MerchantpromotionBDTO?.let {
val promImageUrl = it.image?.getImageUrl(CommonUtils.getDisplayDensityLevel(context))
picasso.load(if (promImageUrl.isNullOrEmpty()) null else promImageUrl)
.placeholder(R.drawable.ic_placeholder_minimal)
.into(ivMerchantPromotionBanner)
}
}
I want the response into my logcat and to fetch an image in Imageview
The issue is that the JSON you're getting from the backend is:
{
"promotions": [
...
]
}
Thas is a JSON object, not an array, but you defined getPromotionalBanner method as returning a List<MerchantpromotionBDTO>, so the JSON library cannot perform deserialisation as it expects an array (i.e. something starting with [) but it finds a START_OBJECT token (i.e., {).
You can solve the issue by changing the signature of that method to:
#GET("https://demo6861386.mockable.io/banner/test")
fun getPromotionalBanner(): Call<MerchantpromotionBDTO>