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
Related
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)
I'm trying to display a 4x4 grid with values that change depending on user input. To achieve that, I created mutableStateListOf that I use in a ViewModel to survive configuration changes. However, when I try to replace a value in that particular list using button onClick, it keeps doing that until app crashes. I can't understand why is onReplaceGridContent looping after clicking the button once. Currently, my code looks like this:
ViewModel:
class GameViewModel : ViewModel(){
var gameGridContent = mutableStateListOf<Int>()
private set // Restrict writes to this state object to private setter only inside view model
fun replaceGridContent(int: Int, index: Int){
gameGridContent[index] = int
}
fun removeGridContent(index: Int){
gameGridContent[index] = -1
}
fun initialize(){
for(i in 0..15){
gameGridContent.add(-1)
}
val firstEmptyGridTile = GameUtils.getRandomTilePosition(gameGridContent)
val firstGridNumber = GameUtils.getRandomTileNumber()
gameGridContent[firstEmptyGridTile] = firstGridNumber
}
}
Button:
Button(
onClick = {
onReplaceGridContent(GameUtils.getRandomTileNumber(),GameUtils.getRandomTilePosition(gameGridContent))},
colors = Color.DarkGray
){
Text(text = "Add number to tile")
}
Activity Composable:
#Composable
fun gameScreen(gameViewModel: GameViewModel){
gameViewModel.initialize()
MainStage(
gameGridContent = gameViewModel.gameGridContent,
onReplaceGridContent = gameViewModel::replaceGridContent,
onRemoveGridContent = gameViewModel::removeGridContent
)
}
Your initialize will actually run on every recomposition of gameScreen:
You click on a tile - state changes causing recomposition.
initializa is called and changes the state again causing recomposition.
Step 2 happens again and again.
You should initialize your view model in its constructor instead (or use boolean flag to force one tim initialization) to make it inly once.
Simply change it to constructor:
class GameViewModel : ViewModel(){
var gameGridContent = mutableStateListOf<Int>()
private set // Restrict writes to this state object to private setter only inside view model
fun replaceGridContent(int: Int, index: Int){
gameGridContent[index] = int
}
fun removeGridContent(index: Int){
gameGridContent[index] = -1
}
init {
for(i in 0..15){
gameGridContent.add(-1)
}
val firstEmptyGridTile = GameUtils.getRandomTilePosition(gameGridContent)
val firstGridNumber = GameUtils.getRandomTileNumber()
gameGridContent[firstEmptyGridTile] = firstGridNumber
}
}
Now you don't need to call initialize in the composable:
#Composable
fun gameScreen(gameViewModel: GameViewModel){
MainStage(
gameGridContent = gameViewModel.gameGridContent,
onReplaceGridContent = gameViewModel::replaceGridContent,
onRemoveGridContent = gameViewModel::removeGridContent
)
}
I have a multiple TextInput named input_x where x is a number (e.g input_1, input_2 etc.,). I also have multiple buttons named btn_x where x is a number (e.g btn_1, btn_2 etc.,).
When a btn_x is pressed, I should be able to print the value of the input that has matches the x value (e.g when btn_2 is pressed, value inside input_2 will be printed.)
This is my current attempt that does not work:
fun handleClickButton (view: View) {
with (view as Button) {
var this_btn = view.resources.getResourceEntryName(id)
val id_string = this_btn
val delim = "_"
val arr = id_string.split(delim).toTypedArray()
val id_num = arr[2]
val this_input = "input_" + arr[2].toString()
print(binding.this_input.text)
}
}
Any help would be really really appreciated! I am very new to android development and Kotlin.
Here are the issues that need to fix:
Getting arr[2] while the length of the arr is 2; So this should be arr[1] as arrays are 0-baed indices to avoid ArrayIndexOutOfBoundsException
A text value is invoked on the binding object with this expression binding.this_input and binding requires to have a view id:
Not sure if you can use view bindings for an int as view ID, because this reference name of that view is not registered in the generated binding class.
But at least you can achieve that with findViewById:
Applying that in your code:
fun handleClickButton(view: View) {
with(view as Button) {
val this_btn = view.resources.getResourceEntryName(id)
val id_string = this_btn
val delim = "_"
val arr = id_string.split(delim).toTypedArray()
val id_num = arr[1]
val this_input = "input_" + arr[1]
val id = resources.getIdentifier(this_input, "id", context.packageName)
val input = this#MainActivity.findViewById<EditText>(id)
print(input.text)
}
}
Note: I'm assuming the surrounding activity as MainActivity, you can replace it with your activity as omitting it will get findViewById() called on the button.
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 ){
}