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
}
Related
I wanted to know what is the difference between the two approaches for settings values in the viewmodels:
Approach one is using function to set the new value to the variable. The second approach is using the setter to set the value to the variable.
I know it is not recommended to expose mutable variables to the view but the execution is the same if we call the function or set the variable in the views.
A:
``
class SampleViewModel(): ViewModel {
private val _title = MutableLiveData<String>()
val title: LiveData<String>
get() = _title
// Setting the title
fun setTitle(newTitle: String) {
_title.value = newTitle
}
}
B:
class SampleViewModel(): ViewModel {
private val _title = MutableLiveData<String>()
val title: LiveData<String>
get() = _title
// Setting the title
var setTitle: String
set(value) = {
field = value
_title.value = value
}
}
Any input is appreciated.
I tried both approaches and it is working fine on both cases.
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)
I have a data class, where as a JSON from backend I get string as key for example "ready".
if key = "ready", then it should fullfil the rest of the data like:
key = "ready"
order = 1
isReady = true
isClosed = false
isFinished = false
I hope I could do it dynamically like that:
#TypeConverters(Converters::class)
#Entity(tableName = "statemanager")
data class StateManager (
#PrimaryKey
var key: String = "",
var order: Int = 0,
var isReady: Boolean,
var isClosed: Boolean,
var isFinished: Boolean,
) {
init {
order = getOrderFromKey(key)
isReady= key == READY
isClosed = key == CLOSED
isFinished= key == FINISHED
}
companion object {
fun getOrderFromKey(key: String): Int {
return when (key) {
READY -> 1
CLOSED -> 2
FINISHED -> 3
else -> throw IllegalArgumentException()
}
}
}
}
I needed to write converter so it is:
class Converters {
private val gson = GsonBuilder().serializeNulls().create()
#TypeConverter
fun convertManagerToString(type: StateManager?): String =
gson.toJson(type)
#TypeConverter
fun convertFromJSONToManager(key: String): StateManager? =
gson.fromJson(key, StateManager::class.java)
}
I've got error. This data is part of another data class where I use it like
#ColumnInfo(name = "state")
var state: StateManager
error:
Expected BEGIN_OBJECT but was STRING at line 1 column 361
EDIT:
I have fixed it, but IMO it not nice way. I use #JSONAdapter adnotation and there I have changed my string to json.asString and everything works, but do you know nicer way to do it? How to do it without "#JSONAdapter"?
I am trying to set the float value to an edit text. For this I am using a binding adapter like this below.
#BindingAdapter("android:text")
#JvmStatic
fun setAmount(editText: EditText, currency: Float?) {
if (currency!= null && currency!=0.0f) {
editText.setText(""+Math.round(currency))
}else{
editText.setText("")
}
}
Model Class
class OpportunityModel : BaseObservable(), Serializable {
var accountName = ""
var accountGuid: String? = null
var amount = 0.0
var potentialAmount:Float = 0.0f
get() = field
set(value) {field=value}
var contactPersonName = ""
var fieldOne = ""
var fieldTwo = ""
var fieldThree = ""
var fieldFour = ""
var fieldFive = ""
var opportunityName = ""
var opportunityGuid: String? = null
var opportunityRating = 0
var opportunityReasonGuid: String? = null
var opportunityIntStatus = 2
var opportunityDispStatus = ""
var opportunityNotAvailable = false
var genericFieldUI: GenericFieldDto? = null
#SerializedName("expDateOfClosure")
var dateForServer: String? = null
var expDate = ""
var contactPersonNameGuid: String? = null
var listOfAccountContact = ArrayList<AccountContactPersonModel>()
var listOfReasonMaster = ArrayList<ReasonMasterDto>()}
This shows the value properly in the edit text but when this value is added to the model class via data binding, it gets converted to scientific notation and is showing values like 1E+07. How can I stop this conversion to scientific notation ?
You can use String.format, like
#BindingAdapter("android:text")
#JvmStatic
fun setAmount(editText: EditText, currency: Float?) {
if (currency!= null && currency!=0.0f) {
editText.setText(String.format("%.8f", currency))
}else{
editText.setText("")
}
}
I'm trying to do a quite simple task: assign properties to an object and return that same object after retrieving the infos with a REST call.
In my runBlocking block I use the apply function to change the properties of my object, but after trying different ways to assign them, instantiate the object itself, modifying constructing logic of the object, I still get an object with the default values.
Here's my Info object:
class DrivingLicenceInfo {
var type : String = ""
var nationality : String = ""
var number : String = ""
var releaseDate : String = ""
var expiryDate : String = ""
}
Here's the method which gives me problems:
private fun getDerivingLicenceInfoAndWaitForCompletion(): DrivingLicenceInfo {
return runBlocking {
val response = retrieveDrivingLicenceInfoAsync().await()
if (response.isSuccessful) {
var info = DrivingLicenceInfo()
response.body()?.let {
info.apply {
it.data.let { data ->
val type = data.guy
val drivingLicenseNationality = data.drivingLicenseNationality
val drivingLicenseNumber = data.drivingLicenseNumber
val drivingReleaseDate = data.drivingReleaseDate
val drivingExpiryDate = data.drivingExpiryDate
this.type = type
this.nationality = drivingLicenseNationality
this.number = drivingLicenseNumber
this.releaseDate = drivingReleaseDate
this.expiryDate = drivingExpiryDate
}
}
info
Log.i("driving.info.call", info.type)
}
}
DrivingLicenceInfo()
}
}
And here's where I use it, in my Main, and where I get an info object with empty strings as properties
private void getDrivingLicenceData() {
DrivingLicenceInfoService service = new DrivingLicenceInfoServiceImpl(context);
DrivingLicenceInfo info = service.getDrivingLicenceInfo();
Log.i("driving.info.main",info.getType());
profileViewModel.licenceNumber.postValue(info.getNumber());
profileViewModel.licenceExpiryDate.postValue(info.getExpiryDate());
}
The log in the runBlocking correctly shows the property, the log in my Main doesn't even show up.
Using the debugger I am able to see that info has empty strings as value.
Could somebody help me to figure out what I'm doing wrong?
Thank you
Beside #JeelVankhede giving you the main reason for your problem, I suggest some minor code improvements as well. I personally feel this is ways less verbose and better readable
private fun getDrivingLicenceInfoAndWaitForCompletion(): DrivingLicenceInfo {
return runBlocking {
val response = retrieveDrivingLicenceInfoAsync().await()
var info = DrivingLicenceInfo()
return if (response.isSuccessful) {
response.body()?.let {
info.apply {
type = it.data.guy
nationality = it.data.drivingLicenseNationality
number = it.data.drivingLicenseNumber
releaseDate = it.data.drivingReleaseDate
expiryDate = it.data.drivingExpiryDate
}
Log.i("driving.info.call", info.type)
info
} ?: info
} else { info }
}
}
Since #JeelVankhede already told you the main reason of your problem and I also have some suggestions apart from the one given by #WarrenFaith.
If DrivingLicenceInfo is a model class you can declare it as data class like
data class DrivingLicenceInfo (
val type : String = "",
val nationality : String = "",
val number : String = "",
val releaseDate : String = "",
val expiryDate : String = ""
)
you can read more about data class here.
And then you can write your function as
private fun getDerivingLicenceInfoAndWaitForCompletion(): DrivingLicenceInfo {
val info = runBlocking {
val response = retrieveDrivingLicenceInfoAsync().await()
if (response.isSuccessful) {
response.body()?.let {
it.data.let { data ->
DrivingLicenceInfo(
type = data.guy,
nationality = data.drivingLicenseNationality,
number = data.drivingLicenseNumber,
releaseDate = data.drivingReleaseDate,
expiryDate = data.drivingExpiryDate
)
}
} ?: DrivingLicenceInfo()
} else {
DrivingLicenceInfo()
}
}
Log.i("driving.info.call", info.type)
return info
}