I declared a data class in a Kotlin function, but the data is empty after gson conversion.
fun writeAndFlush(context: StateMachine) {
data class Temp(val model: TaskModel, val totalTime: String?, val state: String)
val temp = Temp(context.businessObj, context.totalTime, context.state.toString())
Log.e("test", temp.toString()) // print data here.
val json = Gson().toJson(temp)
Log.e("test", json) // problem here.....print null
}
Is there any problem with this way?
Related
I need set serializable interface for using any data class in methods, example data:
#Serializable
interface Todo{}
#Serializable
data class userDataForRegistration(val name: String, val number: String, val password: String): Todo
#Serializable
data class userDataForLogin(val number: String, val password: String): Todo
#Serializable
data class contactForRemove(val id: String, val number: String): Todo
#Serializable
data class userData(val number: String)
#Serializable
data class message(val message: String)
example method, where body - some of the above data classes :
class Connection {
val client = OkHttpClient()
// params: login, registration, contact
fun sendData(url: String, param: String, body: Todo){
var json = Json.encodeToString(body)
var reqBody = RequestBody.create("application/json; charset=utf-8".toMediaTypeOrNull(), json)
val request = Request.Builder()
.url(url)
.post(reqBody)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
println("error" + e)
}
override fun onResponse(call: Call, response: Response){
var res = response.body?.string()
when(param){
"login", "registration" -> {
try{
val objUser = Json.decodeFromString<User>(res.toString())
returnUser(objUser)
}
catch(e: Exception){
val mes = Json.decodeFromString<message>(res.toString())
returnMessage(mes)
}
}
"contact" ->{
val mes = Json.decodeFromString<message>(res.toString())
returnMessage(mes)
}
}
}
})
}
but if i calling method:
val userDataForLogin = userDataForLogin(etv_name.text.toString(), etv_pass.text.toString())
val response = connection.sendData("${ip_static.ip}/user/login", "login", userDataForLogin)
i get error:
#Serializable annotation is ignored because it is impossible to serialize automatically interfaces or enums. Provide serializer manually via e.g. companion object
I need use only TODO interface to use any data class in methods, object and abstract class will doesnt working, because it use data class
Also my plugins in build.gradle:
plugins {
id 'com.android.application' version '7.2.2' apply false
id 'com.android.library' version '7.2.2' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.21'
}
I read, that kotlin.plugin.serialization 1.6.2+ working with serialization interface, but idk whats wrong with me...
Thank you in advance!)
You should not add #Serializable to the interface, see the documentation regarding polymorphic seriliazation. Instead you must annotate the implementations and possibly register the corresponding serializers.
Is there a way to register a codec for multiple classes? Basically, all my classes should just be serialized using a Jackson object mapper. But it seems like I have to create a custom codec for each class (even though I can abstract it a little bit using generics).
A small code example:
Codec:
class JacksonCodec<T>(private val mapper: ObjectMapper, private val clazz: Class<T>) : MessageCodec<T, T> {
override fun encodeToWire(buffer: Buffer, s: T) {
buffer.appendBytes(mapper.writeValueAsBytes(s))
}
override fun decodeFromWire(pos: Int, buffer: Buffer): T {
val length = buffer.getInt(pos)
val bytes = buffer.getBytes(pos + 4, pos + 4 + length)
return mapper.readValue(bytes, clazz)
}
...
}
register codec for each class I want to serialize:
vertx.eventBus()
.registerDefaultCodec(A::class.java, JacksonCodec(DatabindCodec.mapper(), A::class.java))
vertx.eventBus()
vertx.eventBus()
.registerDefaultCodec(B::class.java, JacksonCodec(DatabindCodec.mapper(), B::class.java))
vertx.eventBus()
The code examples are kotlin but same applies for Java.
As far as I can tell looking at the code, there is no way, as the class needs to be the exact match:
https://github.com/eclipse-vertx/vert.x/blob/master/src/main/java/io/vertx/core/eventbus/impl/CodecManager.java#L99
It is possible, with some limitations and quirks. I would not recommend doing it.
Let's start with the limitations:
It can not be used in clustered mode
You have to declare the codec name every time you send something over the eventbus.
If you create a generic codec that encodes classes with Jackson and every time you send something over the eventbus you make sure to add it using codecName in the deliveryOptions, you can register it only once and use it for all of your classes.
Full example:
fun main() {
val vertx = Vertx.vertx()
vertx.eventBus().registerCodec(GenericCodec())
vertx.eventBus().consumer<Foo>("test-address") {
println(it.body())
it.reply(Bar(), genericDeliveryOptions)
}
vertx.eventBus().request<String>("test-address", Foo(), genericDeliveryOptions) {
println(it.result().body())
}
vertx.close()
}
data class Foo(
val foo: String = "foo",
)
data class Bar(
val bar: String = "bar",
)
class GenericCodec : MessageCodec<Any, Any> {
companion object {
const val NAME = "generic"
}
private val mapper: ObjectMapper = ObjectMapper()
override fun encodeToWire(buffer: Buffer, s: Any) {
buffer.appendBytes(mapper.writeValueAsBytes(s))
}
override fun decodeFromWire(pos: Int, buffer: Buffer): Any {
throw RuntimeException("should never get here, unless using clustered mode")
}
override fun transform(s: Any): Any {
return s
}
override fun name(): String {
return NAME
}
override fun systemCodecID(): Byte {
return -1
}
}
val genericDeliveryOptions = deliveryOptionsOf(codecName = GenericCodec.NAME)
i am trying to display data from API using retrofit2 in koltin
but the API shows that there is categoryId before the data i am calling as showing below:
[
{
"categoryId": 0,
"categoryName": "string",
"ilmFinders": [
{
"eventId": 0,
"eventName": "string",
"eventLocation": "string",
"eventPhoto": {},
"eventDescription": "string",
"eventDate": "string",
"eventLink": "string",
"isFavourite": {}
}
]
}
]
i tried to call only (eventName, eventPhoto, eventLink) in my data class as showing below:
data class IimfinderData(
val eventLink: String,
val eventPhoto: String,
val eventName: String
)
API interface:
interface finderService {
#GET(" ")
fun getServices() : Call<List<IimfinderData>>
companion object Factory {
fun create(): finderService {
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("PRIVATE URL")
.build()
return retrofit.create(finderService::class.java)
}
}
}
Adapter:
class IimfinderAdapter(var countryList: List<IimfinderData>, var activity: MainActivity): RecyclerView.Adapter<IimfinderAdapter.ViewHolder>(){
lateinit var context: Context
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IimfinderAdapter.ViewHolder {
context = parent.context!!
val view = LayoutInflater.from(context).inflate(R.layout.list_item2, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return countryList.size
}
override fun onBindViewHolder(holder: IimfinderAdapter.ViewHolder, position: Int) {
holder.eventName.text = countryList[position].eventName
holder.eventLink.text = countryList[position].eventLink
Picasso.with(activity)
.load(countryList[position].eventPhoto)
.error(R.drawable.qiblacompass)
.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
.networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE)
.into(holder.eventPhoto)
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val eventName: TextView = itemView.findViewById(R.id.title_tv2)
val eventLink: TextView = itemView.findViewById(R.id.tv_url2)
val eventPhoto: ImageView = itemView.findViewById(R.id.thumbnail_tv2)
}
}
after i run the application, it shows that the data are null
do i need to call "categoryId"?
and if yes, how can i call 2 arrays in the same data class?
the Adapter and the code in my activity is correct because i tested it with another API
so what am i doing wrong here?
------------------------------- Solution ------------------------
refer to the answer down and i have to change how i call the data from the adapter
instead of this:
holder.eventName.text = countryList[position].eventName
do this:
holder.eventName.text = countryList[position].ilmFinders[position].eventName
Check point
data that you want is in List of List
jsonArray > jsonArray > IimfinderDataJson
IimfinderData by List
eventPhoto is Object, not String
try this code
Call<List<IimfinderData>> -> Call<List<IlmFinders>>
fun getServices() : Call<List<IlmFinders>>
and
data class IlmFinders(
#SerializedName("ilmFinders")
val ilmFinders: List<IimfinderData>
)
data class IimfinderData(
#SerializedName("eventLink")
val eventLink: String,
#SerializedName("eventPhoto")
val eventPhoto: Any,
#SerializedName("eventName")
val eventName: String
)
if your data class properties name is same with json element, you don't need #SerializedName
As you are trying to access jsonArry in API response you can do as below:
Create data class for "IimfinderData" with serialization as:
data class ResponseData(
#SerializedName("name")
val userName: String,
#SerializedName("age")
val userAge: Integer,
#SerializedName("date_of_birth")
val userDOB: String)
than use this class as the list of data in data class as:
data class Response(
#SerializedName("userData")
val ilmFinders: List<ResponseData>)
access this data class with function as :
fun getServices() : Call<List<Response>>
Make sure to use #SerializedName Annotation if you are not using same name as response data
Hope this will solve your problem !!
I want to add multilingual support to my app using the Android Studio Translations editor. But it only supports .xml strings. I have some strings in data class and I use data class for making ArrayList. I really want to call these strings from .xml but I couldn't figure out how.
my data class:
data class Infodc (
val id: Int,
val header: String,
val image: Int,
val infoOne: String,
val infoTwo: String
)
my list
object Constants{
fun getInfo(): ArrayList<Infodc>{
val infoList = ArrayList<Infodc>()
val inf1 = Infodc(
1, "header_str", R.drawable.image0,
"string_1", "string_2")
infoList.add(inf1)
return infoList
}
}
I tried R.string.string_header for header = "header_str" but that only show 10 digits, not the string itself.
After that, I tried getString but again I failed.
You can do it like this:
class MainActivity : AppCompatActivity()
{
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val infodc = Infodc(
1,
R.string.header,
R.drawable.ic_launcher_foreground,
R.string.info_one,
R.string.info_two,
this
)
Log.d("MyTag", "$infodc") // D/MyTag: Infodc(id=1, header=Header, image=2131099744, infoOne=Info One, infoTwo=Info Two)
}
}
data class Infodc(
val id: Int,
val header: String,
val image: Int,
val infoOne: String,
val infoTwo: String
)
{
constructor(
id: Int,
#StringRes header: Int,
#DrawableRes image: Int,
#StringRes infoOne: Int,
#StringRes infoTwo: Int,
context: Context
) : this(
id,
context.getString(header),
image,
context.getString(infoOne),
context.getString(infoTwo)
)
}
Now You can pass to a constructor id of String and image but as a class field You will get String
Another way is to have a class that only contains an id of string and then when You want to use it use getString of that field.
I am very new to Gson and Json. I have simple Events that I want to serialize through Json with the help of Gson.
Note: Code in Kotlin.
public abstract class Event() {
}
public class Move : Event() {
var from: Point? = null
var to: Point? = null
}
public class Fire : Event() {
var damage: Int = 0
var area: ArrayList<Point> = ArrayList(0)
}
public class Build : Event() {
var to: Point? = null
var type: String = ""
var owner: String = ""
}
I am persisting bunch of these via this way:
val list: ArrayList<Event>() = ArrayList()
list.add(move)
list.add(fire)
val str = gson.toJson(events)
And unpersisting:
val type = object : TypeToken<ArrayList<Event>>(){}.getType()
val eventStr = obj.getString("events")
val events: ArrayList<Event> = gson.fromJson(eventStr, type)
I have tried both creating a serializer & deserializer for Event-class, and registering it via registerTypeAdapter, and I have also tried the RuntimeTypeAdapterFactory, but neither will persist the information required to unpersist the correct type.
For example, the RuntimeTypeAdapterFactory says:
"cannot deserialize Event because it does not define a field named type"
EDIT: Here's the code for the "Adapter", which was.. well, adapted from another StackOverflow post:
public class Adapter :
JsonSerializer<Event>,
JsonDeserializer<Event> {
final val CLASSNAME = "CLASSNAME"
final val INSTANCE = "INSTANCE"
override fun serialize(src: Event?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement? {
val obj = JsonObject()
val className = (src as Event).javaClass.getCanonicalName()
obj.addProperty(CLASSNAME, className)
val elem = context!!.serialize(src)
obj.add(INSTANCE, elem)
return obj
}
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Event? {
val jsonObject = json!!.getAsJsonObject()
val prim = jsonObject.get(CLASSNAME)
val className = prim.getAsString()
val klass = Class.forName(className)
return context!!.deserialize(jsonObject.get(INSTANCE), klass)
}
}
This code fails with NullPointerException on line:
val className = prim.getAsString()
You can't do it this way.
The example you are referring is not targeted to your case. It works in only one case: if you register base type (not type hierarchy) and serialize using gson.toJson(obj, javaClass<Event>()). It will never work for array except you write custom serializer for you events container object too
Generally you need another approach: use TypeAdapterFactory and delegate adapters: GSON: serialize/deserialize object of class, that have registered type hierarchy adapter, using ReflectiveTypeAdapterFactory.Adapter and https://code.google.com/p/google-gson/issues/detail?id=43#c15
I believe this approach is overcomplicated so if you have few types the easiest solution is two serialize these types by hand, field by field via custom serializer and forget about attempts to delegate to default