How to save custom objects into preferences in Kotlin? - android

I have a global object that store 2 custom class objects, something like this:
/////// Separated object file ////////
object ApplicationData {
var profiledata:ProfileData = ProfileData(null)
var OVOlists = ArrayList<OVOList>()
}
/////// Separated class file ////////
class OVOList (
private var OVOIcon: Int,
private var OVOName: String?,
private var OVOPlace: String?,
private var OVOstatus: Int
) :Parcelable {
private var Humidity:String? =""
private var Temperature:String? = ""
private var PH:String? =""
private var WaterLvl :String? = ""
constructor(parcel: Parcel) : this(
parcel.readValue(Int::class.java.classLoader) as Int,
parcel.readString(),
parcel.readString(),
parcel.readValue(Int::class.java.classLoader) as Int
) {
Humidity = parcel.readString()
Temperature = parcel.readString()
PH = parcel.readString()
WaterLvl = parcel.readString()
} ... + setters , getters and parcel lines
/////// separated class file ////////
class ProfileData(private var email:String?):Parcelable {
private var token:String? =""
private var profile_image: String? =""
private var profile_image_path:String? = ""
private var nombre:String? = ""
private var uid:String? = ""
constructor(parcel: Parcel) : this(
parcel.readString(),
) {
token=parcel.readString()
profile_image = parcel.readString()
profile_image_path = parcel.readString()
nombre = parcel.readString()
uid=parcel.readString()
} ... + setters,getters and parcel lines
The classes are parcelable, because i was moving some information via bundles, but now im using this global object so there is no need to keep them like that.
But the question is how to store the whole object into memory, i have try the Gson/preferences aproach but i cannot make it work :(, it doesnt store the object, maybe its because it has 2 custom class objects inside, i dont think the parcelable attribute should affect. I made something like this:
//write//
mprefs.edit().putString("MyappData",gson.toJson(ApplicationData)).apply()
//read//
String json = mprefs.getString("MyappData", "")
val obj = gson.fromJson(json, ApplicationData::java.class)
ApplicationData.profiledata = obj.profiledata
ApplicationData.OVOlists = obj.OVOlists
It seems that its failing in the writing part , any ideas what to do?

You can save them as Strings (json format ) and the covert to Object ,as you are doing , I think you should use data classes and use #Parcelize and avoid using too much code

Thanks to all your help, i made this work with Gson, i created inside the object a function that stores and retrieves the data from/to pref. i just pass the context to it, i think was not working properly because of the parcelable part inside the classes. The final object is this:
object ApplicationData {
var profiledata:ProfileData = ProfileData(null)
var OVOlists = ArrayList<OVOList>()
fun save(context: Context){
val sharedPreference = context.getSharedPreferences("OVO_PREF", Context.MODE_PRIVATE)
val prefsEditor = sharedPreference.edit()
val gson = Gson()
val json = gson.toJson(ApplicationData)
prefsEditor.putString("AppData", json)
Log.e("saving in preferences",json)
prefsEditor.apply()
}
fun retrieveData(context: Context):Boolean{
val sharedPreference = context.getSharedPreferences("OVO_PREF", Context.MODE_PRIVATE)
val gson = Gson()
val json = sharedPreference.getString("AppData", "")
val obj = gson.fromJson(json, ApplicationData::class.java)
if(obj!=null){
profiledata = obj.profiledata
OVOlists = obj.OVOlists
//Log.e("retrieve info",obj.OVOlists[0].getOVOName()!!)
return true
}else{
return false }
}
The two custom classes now are regular and normal classes, with setters and getters. So the main object can save its own data calling ApplicationData.save(context)

Related

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

How to save old data in a data class in Kotlin

I am trying to use a data class but I can't figure out how to save the data properly.
I have created a data class:
data class SavedValue( var currentValue:String, var previousValue:String = "")
What I want is each time I am want to save a new currentValue, the already saved value for current is copy to previousValue and the new currentValue overwrite the currentValue field.
Thanks for the help
A data class in Kotlin is not supposed to provide such functionalities, as they are designed to hold data.
You could just use a simple class.
Nevertheless you can achieve what you want (with or without a data class), but you will have to move currentValue inside the class and use a setter (and a getter).
In its place use a private property like _currentValue:
data class SavedValue(private var _currentValue: String, var previousValue: String = "") {
var currentValue: String
get() = _currentValue
set(value) {
previousValue = _currentValue
_currentValue = value
}
}
This code:
val sv = SavedValue("abc")
println("currentValue = ${sv.currentValue} and previousValue = ${sv.previousValue}")
will print:
currentValue = abc and previousValue =
and this:
sv.currentValue = "ABC"
println("currentValue = ${sv.currentValue} and previousValue = ${sv.previousValue}")
will print:
currentValue = ABC and previousValue = abc
Also, I think that you need previousValue as a read only property, right?
So move it too inside the class and make its setter private:
data class SavedValue(private var _currentValue: String) {
var _previousValue: String = ""
var currentValue: String
get() = _currentValue
set(value) {
previousValue = _currentValue
_currentValue = value
}
var previousValue: String
get() = _previousValue
private set(value) {
_previousValue = value
}
}
What you are trying to achieve isn't straight forward using data class. Instead, you can use POJO class and use custom setter and getter.
class SavedValue(currentValue: String, previousValue: String) {
private var _currentValue: String = currentValue
private var _previousValue: String = previousValue
var currentValue: String
get() {
return _currentValue
}
set(value) {
_previousValue = _currentValue
_currentValue = value
}
override fun toString(): String {
return "SavedValue(_currentValue='$_currentValue',
_previousValue='$_previousValue')"
}
}
The first solution works fine but if you do not want the third field just to hold the current value you can do:
data class SavedValue(var previousValue: String = "") {
var currentValue: String = ""
set(value) {
if (field != value) previousValue = field
field = value
}
}
E.g.
val savedValue = SavedValue()
savedValue.currentValue = "initial value"
println("current: ${savedValue.currentValue} - previous: ${savedValue.previousValue}")
savedValue.currentValue = "second value"
println("current: ${savedValue.currentValue} - previous: ${savedValue.previousValue}")
savedValue.currentValue = "third value"
println("current: ${savedValue.currentValue} - previous: ${savedValue.previousValue}")
Outputs:
I/System.out: current: initial value - previous:
I/System.out: current: second value - previous: initial value
I/System.out: current: third value - previous: second value
Or if you want the non-mutable previousValue you'll need the third field:
data class SavedValue(private var _previousValue: String = "") {
var currentValue: String = ""
set(value) {
if (field != value) _previousValue = field
field = value
}
val previousValue: String
get() = _previousValue
}

Is it possible to same key with differ class and data type in data class kotlin android?

I have one issue about code data class kotlin android.
How to implement server response? sometimes I get String value or sometime get Object class.
class CMSRespTemp {
data class CMSRespApi(
val status: Boolean = false,
val message: String = "",
val data: String as Data
)
data class Data(
val cms_id: String = "",
val cms_content: String = ""
)
}
When I implement only Data class it works, like this val data: Data or val data: String. But I need together Data and String with key only data.
Is it possible?
When having multiple type for same variable, we can use Any type which is equivalent to Object type in java. So solution is like below :
class CMSRespTemp {
data class CMSRespApi(
val status: Boolean = false,
val message: String = "",
var data: Any? = null // changed it to var from val, so that we can change it's type runtime if required
)
data class Data(
val cms_id: String = "",
val cms_content: String = ""
)
}
And when accessing that variable, one can simply cast like below :
val apiResponse : CMSRespApi //= some API response here from network call
when (apiResponse.data) {
is String -> {
// apiResponse.data will be smart-casted to String here
}
else -> {
val responseData = Gson().fromJson<CMSRespApi.Data>(
Gson().toJsonTree(apiResponse.data),
CMSRespApi.Data::class.java
)
}
}
After 12 Hrs spend and got the solution my self,
val getResultCon = getSerCont.result // response Any
val gson = Gson()
val jsonElement = gson.toJsonTree(getResultCon)
val resultData = gson.fromJson(jsonElement, SearchContactApi.Result::class.java)
Convert your data string to toJsonTree and fromJson with model class then got result.

Deserialize a Firebase Data-Snapshot to a Kotlin data class

Hi I have a Kotlin data class as follows
data class User (
#get:Exclude val gUser: Boolean,
#get:Exclude val uid: String,
#get:PropertyName("display_name") val displayName: String,
#get:PropertyName("email") val email: String,
#get:PropertyName("account_picture_url") val accountPicUrl: String,
#get:PropertyName("provider") val provider: String
)
I am able to serialize the object without an issues. But i'm having trouble deserializing the object when doing a firebase query. Currently this is what i'm doing to get the data
_firebaseReference.child(getString(R.string.firebase_users_key)).child(user.uid)
.setValue(user).addOnCompleteListener{
_firebaseReference.child("users").child(user.uid)
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(p0: DataSnapshot) {
if (p0.exists()) {
val userHash = p0.value as HashMap<*, *>
var currentUser: User
if (userHash[getString(R.string.provider_key)]
!= getString(R.string.provider_google)) {
currentUser = User(false, p0.key!!,
userHash["display_name"].toString(),
userHash["email"].toString(),
userHash["account_picture_url"].toString(),
userHash["provider"].toString())
} else {
currentUser = User(true, p0.key!!,
userHash["display_name"].toString(),
userHash["email"].toString(),
userHash["account_picture_url"].toString(),
userHash["provider"].toString())
}
}
}
})
}
This is only a test project that i'm working on to practice my Kotlin, but this is something I would like to figure out.
If i'm doing it completely wrong please let me know, any advise would be greatly appreciated
Thanks
Firebase needs an empty constructor to be able to deserialize the objects:
data class User(
#Exclude val gUser: Boolean,
#Exclude val uid: String,
#PropertyName("display_name") val displayName: String,
#PropertyName("email") val email: String,
#PropertyName("account_picture_url") val accountPicUrl: String,
#PropertyName("provider") val provider: String
) {
constructor() : this(false, "", "", "", "", "")
}
You can either declare it like so and provide some default values to be able to call the primary constructor or you can declare default values for all your parameters:
data class User (
#Exclude val gUser: Boolean = false,
#Exclude val uid: String = "",
#PropertyName("display_name") val displayName: String = "",
#PropertyName("email") val email: String = "",
#PropertyName("account_picture_url") val accountPicUrl: String = "",
#PropertyName("provider") val provider: String = ""
)
Then various constructors will be created for you, including an empty constructor.
If there's a problem with serialization there might be because of the getters and setters generated by the ide, try reinforcing them with #get and #set annotations:
data class User (
#Exclude val gUser: Boolean = false,
#Exclude val uid: String = "",
#set:PropertyName("display_name")
#get:PropertyName("display_name")
var displayName: String = "",
#PropertyName("email") val email: String = "",
#set:PropertyName("account_picture_url")
#get:PropertyName("account_picture_url")
var accountPicUrl: String = "",
#PropertyName("provider") val provider: String = ""
)
What I actually wanted is a Kotlin data class which is derived from a domain model interface like so
data class Dto(#PropertyName("serialized_title") val override title: String) : DomainModel
In this case DomainModel is defined this way
interface DomainModel { val title: String }
My goal was to fetch data from Firestore and get deserialized Dto objects which are provided to clients which receive objects of type DomainModel. So this solution above unfortunately didn't work. I saw the workarounds using #get: and #set: Annotations but I wanted my data class properties to be immutable. Simply using vars is a bad design decision in my use case. And also this solution looks quite ugly...
After inspecting the decompiled Java-Code I came up with this solution
data class Dto(
#field:[JvmField PropertyName("serialized_title")]
override val title: String = "") : DomainModel
The decompiled Java-Code simply uses title as public final field having the PropertyName annotation.
I prefer this solution since it doesn't violate certain design decisions I made...
In Android Studio (kotlin)
use this (only var and getter and setter):
#set:PropertyName("email") #get:PropertyName("email") var emailPerson: String = ""
None of this works:
#PropertyName("email") var emailPerson: String = ""
#PropertyName("email") val emailPerson: String = ""
#get:PropertyName("email") val emailPerson: String = ""
Android Studio 4.1.2. Gradle: com.google.firebase:firebase-database:19.6.0

Kotlin parse Object array using JSONParser from Gson

I have these classes written in kotlin, Location, and the rest is in the Application.kt
#RealmClass
open class Location(
#PrimaryKey
#SerializedName("id")
var id: Int = 0,
#SerializedName("city_name")
var city_name: String? = null,
#SerializedName("elevation")
var elevation: Int = 0,
#SerializedName("state_code")
var state_code: String? = null,
#SerializedName("state_name")
var state_name: String? = null,
#SerializedName("country_code")
var country_code: String? = null,
#SerializedName("country_name")
var country_name: String? = null
):RealmObject()
and the rest:
private fun loadStuff() {
val inputStream = this.resources.openRawResource(R.raw.city_json)
val jsonReader = JsonReader(InputStreamReader(inputStream, "UTF-8"))
val gson = Gson()
Realm.getDefaultInstance().executeTransactionAsync(Realm.Transaction { realm ->
val weatherList = gson.fromJson<List<Location>>(jsonReader , Array<Location>::class.java).toList()
//realm.insertOrUpdate(location)
jsonReader.endArray()
jsonReader.close()
}, Realm.Transaction.OnSuccess {
Log.d("TAG", "Success")
})
}
and I keep getting exception:
com.example.android.sunshine.data.Location[] cannot be cast to java.lang.Iterable
what am I doing wrong ?
the object looks like this:
[
{
"id":3040051,
"city_name":"les Escaldes",
"elevation":0,
"state_code":"08",
"state_name":"ParrĂ²quia d'Escaldes-Engordany",
"country_code":"AD",
"country_name":"Andorra"
},
{
"id":3041563,
"city_name":"Andorra la Vella",
"elevation":0,
"state_code":"07",
"state_name":"ParrĂ²quia d'Andorra la Vella",
"country_code":"AD",
"country_name":"Andorra"
}
]
This:
List<Location>
Is a List of Location. List implements Iterable.
This:
Array<Location>
is an Array of Location. Array does not implement Iterable.
The differences are bigger than that, but his is the one your error is for.
It was enough to swap List with Array and remove .toList() and it worked like magic

Categories

Resources