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.
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 SecondActitivity and when it starts I assign specific values: listTitle, dbJustForPlaing,currentCursorPosition, numberA and so on.
class SecondActivity : OptionMenuHelper() {
val listTitle: String by lazy { intent.getStringExtra(Values.listTitle) }
lateinit var dbJustForPlaing: DataBaseJustForPlaying
var currentCursorPosition: Int = -1
lateinit var cursorOfWholeList: Cursor
var lastCursorPosition: Int = -1
lateinit var oneRowCursor: Cursor
var numberA = 0
var numberB = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_personal_test)
//some functions
}
}
During the use of this activity, variables are changing. Now I want to add button to SecondActivity, if user presses it, all variables must be returned to same state like they were when activity just started. What is the best way to do that?
I can do this:
fun startNewActitivy(){
val intent = Intent(this, SecondActitivity::class.java)
intent.putExtra("listTitle", listTitle)
startActivity(intent)
finish
}
But I am not sure if this is correct way to do. I need piece of code which will help to easy support my app in future and which also will be efficient in device resource consumption
As pointed out by Peyman in the comments, just create a function that set's the default values for the variables. Something along the following lines,
fun setVariableDefaultValues() {
currentCursorPosition = -1
lastCursorPosition = -1
numberA = 0
numberB = 0
}
You can then call the setVariableDefaultValues() method anywhere you want to reset the values.
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