In the ViewModel of my Android app built with Kotlin, The method shown below sets the state to the values passed in to it. But if the calling code passes in a String constant, it is not set to the constant's value. Instead, it is set to an empty string. In the example below, passing in a ZERO_STRING constant actually assigns an empty string "". How can I pass in a constant and still make this work?
Constants File
ZERO_STRING = "0"
ITEM_QUANTITY_STRING = "ItemQuantity"
Method in ViewModel
private val _shoppingListItemState = MutableLiveData(
savedStateHandle.get<ShoppingListItem>("shoppinglistitem")
?: ShoppingListItem()
)
override val shoppingListItemState: LiveData<ShoppingListItem>
get() = _shoppingListItemState
override fun setStateValue(stateToEdit: String, stateValue: Any?) {
var itemToUpdate = _shoppingListItemState.value!!
when(stateToEdit) {
ITEM_QUANTITY_STRING -> itemToUpdate = itemToUpdate.copy(quantity = stateValue.toString())
}
_shoppingListItemState.value = itemToUpdate
}
//This works
setStateValue(ITEM_QUANTITY_STRING, "0")
//This does not work. It assigns "" instead of "0"
setStateValue(ITEM_QUANTITY_STRING, ZERO_STRING)
Related
For example, I load data into a List, it`s wrapped by MutableStateFlow, and I collect these as State in UI Component.
The trouble is, when I change an item in the MutableStateFlow<List>, such as modifying attribute, but don`t add or delete, the UI will not change.
So how can I change the UI when I modify an item of the MutableStateFlow?
These are codes:
ViewModel:
data class TestBean(val id: Int, var name: String)
class VM: ViewModel() {
val testList = MutableStateFlow<List<TestBean>>(emptyList())
fun createTestData() {
val result = mutableListOf<TestBean>()
(0 .. 10).forEach {
result.add(TestBean(it, it.toString()))
}
testList.value = result
}
fun changeTestData(index: Int) {
// first way to change data
testList.value[index].name = System.currentTimeMillis().toString()
// second way to change data
val p = testList.value[index]
p.name = System.currentTimeMillis().toString()
val tmplist = testList.value.toMutableList()
tmplist[index].name = p.name
testList.update { tmplist }
}
}
UI:
setContent {
LaunchedEffect(key1 = Unit) {
vm.createTestData()
}
Column {
vm.testList.collectAsState().value.forEachIndexed { index, it ->
Text(text = it.name, modifier = Modifier.padding(16.dp).clickable {
vm.changeTestData(index)
Log.d("TAG", "click: ${index}")
})
}
}
}
Both Flow and Compose mutable state cannot track changes made inside of containing objects.
But you can replace an object with an updated object. data class is a nice tool to be used, which will provide you all copy out of the box, but you should emit using var and only use val for your fields to avoid mistakes.
Check out Why is immutability important in functional programming?
testList.value[index] = testList.value[index].copy(name = System.currentTimeMillis().toString())
I have a variable and a LiveData, and I want to update the LiveData when I set a value to the variable.
var trigger = ""
val updateValue: LiveData<Value> = service.getValue(trigger)
// when set the value1 to trigger, the updateValue need to update the value.
var trigger = "value1"
val updateValue: LiveData<Value> = service.getValue("value1")
Use switchmap:
var trigger = MutableLiveData<String>()
val updateValue: LiveData<Value> = Transformations.switchMap(trigger) {
service.getValue(it)
}
Then:
trigger.value = "value1"
I'm creating a Calculator Application.
Whenever a user changes the orientation of the device, the current app activity gets destroyed and recreated. So to fix that problem(i.e. get all the data back) I did this:
Declaring some variables
private const val STATE_OPERAND1 = "data"
private const val STATE_PENDING_OPERATION = "PENDING_OPERATION"
private const val STATE_OPERAND1_STORED = "data"
Overriding the 'onSaveInstanceState' function
override fun onSaveInstanceState(outState: Bundle)
super.onSaveInstanceState(outState)
if (operand1 != null) {
outState.putDouble(STATE_OPERAND1, operand1!!)
outState.putBoolean(STATE_OPERAND1_STORED, true)
}
outState.putString(STATE_PENDING_OPERATION, pendingOperation)
}
Overriding the 'onRestoreInstanceState' function
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
operand1 = if (savedInstanceState.getBoolean(STATE_OPERAND1_STORED, false))
savedInstanceState.getDouble(STATE_OPERAND1)
else
null
result.setText(operand1.toString()) // Marked Line 1
pendingOperation = savedInstanceState.getString(STATE_PENDING_OPERATION)
displayOperation.text = pendingOperation
}
The 'Marked Line 1' in the 3rd code is not required, the reason I added is to find out the error. In this code the result test box clears it's previous text and has the text 'null' entered.
However, when I changed the declaration in 1
from private const val STATE_OPERAND1 = "data" to private const val STATE_OPERAND1 = "STATE_OPERAND1"
The result text box showed the text it was supposed to and everything worked properly. The application worked as long as all the three declaration in the 1st code were different. Why is this the case?
With the initial declarations:
private const val STATE_OPERAND1 = "data"
private const val STATE_OPERAND1_STORED = "data"
this line:
outState.putDouble(STATE_OPERAND1, operand1!!)
sets inside the Bundle object the Double value operand1!! by the key "data".
The next line:
outState.putBoolean(STATE_OPERAND1_STORED, true)
sets again (overwrites) inside the Bundle object the Boolean value true by the key "data".
The result is that the previous Double value is now lost and the only value that exists is the new Boolean value.
So what you have is only one Boolean value.
When you changed to:
private const val STATE_OPERAND1 = "STATE_OPERAND1"
private const val STATE_OPERAND1_STORED = "data"
this conflict vanished, because you had 2 different keys and the 2 values were saved properly without overwriting each other.
I am trying to update my pojo class on a particular click in Kotlin but it is giving me error :-
java.lang.stackoverflowerror: stack size 8mb
Here is my Pojo Class
class NavDrawerItem(var icon_normal: Int,var icon_notified: Int, var title: String, var isShowNotify: Boolean){
var title1: String = title
// get() = title // Calls the getter recursively
set(value)
{ title1 = value }
var image: Int = icon_normal
// get() = image
set(value)
{ image = value }
var image_notified: Int = icon_notified
// get() = image
set(value)
{ image_notified = value }
var notify: Boolean = isShowNotify
set(value) {
notify = value
}
}
I am updating my Pojo on the Item click of NavigationDrawer
override fun onItemClick(position: Int) {
mDrawerLayout?.closeDrawer(this!!.containerView!!)
position1 = position
for (i in navDrawerItems.indices) {
val item = navDrawerItems[i]
item.notify=(if (i == position) true else false)
navDrawerItems[i] = item
mAdapter?.notifyDataSetChanged()
}
}
Please help me!!!!
Your setters create infinite loop, which causes the StackOverflowError exceptions.
class NavDrawerItem(var icon_normal: Int,var icon_notified: Int, var title: String, var isShowNotify: Boolean){
var title1: String = title
// get() = title // Calls the getter recursively
set(value)
{ field = value }
var image: Int = icon_normal
// get() = image
set(value)
{ field = value }
var image_notified: Int = icon_notified
// get() = image
set(value)
{ field = value }
var notify: Boolean = isShowNotify
set(value) {
field = value
}
}
The above is setting the field, where your implementation was recursively setting the values.
Also, as ADM mentioned it's better to move the notifyDataSetChanged outside of the loop and not updating at each iteration.
Modify your class as a simple data class.
data class NavDrawerItem(var icon_normal: Int,var icon_notified: Int, var title: String, var isShowNotify: Boolean)
And
override fun onItemClick(position: Int) {
mDrawerLayout?.closeDrawer(this!!.containerView!!)
for (i in navDrawerItems.indices) {
val item = navDrawerItems[i]
item.notify=(i == position)
}
mAdapter?.notifyDataSetChanged()
}
It is always recommended to use data classes for defining pojos.
Because data classes are only made for storing data.
They provides many unique features over normal class in kotlin.
for example, you don't need to define setter and getters, they are automatically added to your data class.
In addition, your data class will automatically override some useful functions like equals, hashCode, toString, and etc.
defining data class is very easy.
data class Foo ( val title : String, val isHungry : Boolean ){
}
I have tried to set a property value as in the following snippet.This SO question is not answering the question.
var person = Person("john", 24)
//sample_text.text = person.getName() + person.getAge()
var kon = person.someProperty
person.someProperty = "crap" //this doesn't allow me to set value
kon = "manulilated" //this allows me to set the value
sample_text.text = kon
class Person(val n: String, val a: Int){
var pname: String = n
var page: Int = a
var someProperty: String = "defaultValue"
get() = field.capitalize()
private set(value){field = value}
fun Init(nm: String, ag: Int){
pname = nm
page = ag
}
fun getAge(): Int{
return page
}
fun getName(): String{
return pname
}
}
Why was I able to set the value of the Person class on the second line but not the first line?
First, the private modifier is your problem.
Change
private set(value){field = value}
To
set(value){field = value}
//public by default
Otherwise you can’t use the setter outside the class. Read here.
For members declared inside a class:
private means visible inside this class only (including all its members);
Second, you’re misunderstanding something:
var kon = person.someProperty
kon = "manulilated"
In these lines you’re not changing the property in the object. After creating the variable kon, being a String pointing to someProperty, you reassign that local variable to something else. This reassignment is unequal to changing the value of person.someProperty! It has totally no effect in the object.
someProperty has a private setter. You can't set it outside the class when the setter is private
This is a good example of trying to write Kotlin in Java style.
Here is how this would like in Kotlin facilitating the language capabilities. One can see how much shorter and clearer the code is. The Person class has only 3 lines.
Using data class spares us from defining hash and equals and toString.
fun test() {
val person = Person("john", 24)
var kon = person.someProperty
person.someProperty = "crap" //this doesn't allow me to set value
kon = "manulilated" //this allows me to set the value (yeah, but not in the class, this is a local string)
val sample_text = SampleText("${person.name}${person.age}")
sample_text.text = kon
println("Person = $person")
println("Kon = $kon")
println("Sample Text = $sample_text")
}
/** class for person with immutable name and age
* The original code only has a getter which allows to use 'val'.
* This can be set in the constructor, so no need for init code. */
data class Person(val name: String, val age: Int) {
/** The field value is converted to uppercase() (capitalize is deprecated)
* when the value comes in, so the get operation is much faster which is
* assumed to happen more often. */
var someProperty: String = "defaultValue"
// setter not private as called from outside class
set(value) { field = value.uppercase() }
}
/** simple class storing a mutable text for whatever reason
* #param text mutable text
*/
data class SampleText(var text: String) {
override fun toString(): String {
return text
}
}
This is the result:
Person = Person(name=john, age=24)
Kon = manulilated
Sample Text = manulilated