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 ){
}
Related
in my ViewModel:
private val _itemList = mutableStateListOf<Post>()
val itemList: List<Post> = _itemList
fun likePost(newPost: Post){
val index = _itemList.indexOf(newPost)
_itemList[index] = _itemList[index].copy(isLiked = true)
}
Here my Post data class:
data class Post(
val id: Int,
val name: String,
val isLiked: Boolean = false,
)
And here my Composable:
val postList = viewModel.itemList
LazyRow(content = {
items(postList.size) { i ->
val postItem = postList[i]
PostItem(
name = postItem.name,
isLiked = postItem.isLiked,
likePost = { viewModel.likePost(postItem)}
)
}
})
The change does not update in the UI instantly, I first have to scroll the updated item out of the screen so it recomposes or switch to another Screen and go back to see the change.
For some reason it doesn't like updating, it will add and delete and update instantly. You have to do it this way when updating for our to update the state.
fun likePost(newPost: Post){
val index = _itemList.indexOf(newPost)
_itemList[index] = _itemList[index].copy()
_itemList[index].isLiked = true
}
You are returning a List<> effectively and not MutableStateList from your ViewModel.
If you want the list to not be mutable from the view, I happen to use MutableStateFlow<List<>> and return StateFlow<List<>>. You could also just convert it to a list in your composable.
Edit:
//backing cached list, or could be data source like database
private val deviceList = mutableListOf<Device>()
private val _deviceListState = MutableStateFlow<List<Device>>(emptyList())
val deviceListState: StateFlow<List<BluetoothDevice>> = _deviceListState
//manipulate and publish
fun doSomething() {
_deviceListState.value = deviceList.filter ...
}
In your UI
val deviceListState = viewModel.deviceListState.collectAsState().value
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 MutableList in my Android project where i'm adding an object called Articolo, then when a new item is added to that list i need to check if one item with same ID exist and if it does i need to update it's quantity.
The issue is that i'm trying to use MutableList.find to find the object with the same ID and when i find it i'm simply add the quantity to existing quantity but instead it remains immutable.
Here is my Articolo.kt
data class Articolo(var barcode: String, var qta: Int) {
constructor() : this ("", 0)
}
And here is my function where i'm adding data to MutableList
private var articoli = mutableListOf<Articolo>()
private fun addBarcode(barcode: String, qta: Int) {
if (barcode.isEmpty()) {
txtBarcode.requestFocus()
return;
}
articoli.find{
it.barcode == barcode
}?.qta?.plus(qta) ?:
articoli.add(Articolo(barcode, qta))
}
So if i add the first object like barcode: 1111, qty: 1 and then another same object instead of having one element array with qty 2 i still have qty 1..
That's because .plus(Int) returns a new value. You're not changing the property.
Instead you should do:
fun addBarcode(barcode: String, qta: Int) {
val existant = articoli.find { it.barcode == barcode }
if (existant != null) existant.qta += qta
else articoli.add(Articolo(barcode, qta))
}
#VaiTon86 has the answer (you're not actually changing the value in the Articolo object) but really, you should probably be using a Map here anyway:
maximum one of each item
lookup by some value (barcode)
that's a map!
There's a few ways you could implement it, here's one:
val articoli = mutableMapOf<String, Articolo>()
private fun addBarcode(barcode: String, qta: Int) {
articoli.getOrPut(barcode) { Articolo(barcode, 0) }
.let { it.qta += qta }
}
So the getOrPut just adds a new zero-quantity Articolo entry if there isn't already one, and then you add qta to what's already there for that entry.
I have a multiple choice quiz with 4 choices per answer. In the ArrayList with the questions and choices, the correct answer is set to the index of the correct option. I want to shuffle the choices but am not sure how to identify the new index of the correct answer. Any thoughts?
Question object
object ConstantsAnalysis {
const val TOTAL_CORRECT: String = "total_correct"
const val TOTAL_OPP: String = "total_opp"
fun getQuestions3(): ArrayList<Questions3> {
val questionList = ArrayList<Questions3>()
val q1 = Questions3(1, null,
"On a graph, the horizontal line along which data are plotted is the _____",
"y axis", "x axis", "origin", "quadrant", 2, R.string.Jones_1995, null)
questionList.addAll(listOf(q1))
questionList.shuffle()
return questionList
}
}
Data class
data class Questions3(
val id: Int, val image: Int?, val question: String, val option1: String, val option2: String,
val option3: String, val option4: String, val correctAnswer: Int, val dialogBox: Int?, val dialogBox2: Int?)
Shuffle choices
val ansorder = arrayOf(question.option1, question.option2, question.option3, question.option4)
ansorder.shuffle()
radio_button1.text = ansorder[0]
radio_button2.text = ansorder[1]
radio_button3.text = ansorder[2]
radio_button4.text = ansorder[3]
Check answer choice
if (questions3!!.correctAnswer != mSelectedOptionPosition) {
//do x
}
Edit (Since correct answer is a string and the index changes after shuffling, answerView(questions3.correctAnswer, R.drawable.correct_option_border.
class QuestionsActivityAnalysis : AppCompatActivity(), View.OnClickListener {
private var mCurrentPosition:Int = 1
private var mQuestionsList:ArrayList<Questions3>? = null
private var mSelectedOptionPosition:Int = 0
private var mCorrectAnswers: Int = 0
private var mSelectedOptionText: String? = null
private fun shuffle() {
val question = mQuestionsList!![mCurrentPosition - 1]
val ansorder = arrayOf(question.option1, question.option2, question.option3, question.option4)
ansorder.shuffle()
radio_button1.text = ansorder[0]
radio_button2.text = ansorder[1]
radio_button3.text = ansorder[2]
radio_button4.text = ansorder[3]
}
override fun onClick(v: View?) {
when(v?.id){
R.id.radio_button1 -> { selectedOptionView(radio_button1, 1)
mSelectedOptionText = radio_button1.text as String?
}
R.id.radio_button2 -> { selectedOptionView(radio_button2, 2)
mSelectedOptionText = radio_button2.text as String?
}
R.id.radio_button3 -> { selectedOptionView(radio_button3, 3)
mSelectedOptionText = radio_button3.text as String?
}
R.id.radio_button4 -> { selectedOptionView(radio_button4, 4)
mSelectedOptionText = radio_button4.text as String?
}
R.id.btn_submit -> {
val questions3 = mQuestionsList?.get(mCurrentPosition - 1)
if (questions3!!.correctAnswer != mSelectedOptionText) {
} else {
mCorrectAnswers++
}
answerView(questions3.correctAnswer, R.drawable.correct_option_border)
private fun answerView(answer: Int, drawableView: Int) {
when(answer){
1 -> {
radio_button1.background = ContextCompat.getDrawable(this, drawableView)
}
2 -> {
radio_button2.background = ContextCompat.getDrawable(this, drawableView)
}
3 -> {
radio_button3.background = ContextCompat.getDrawable(this, drawableView)
}
4 -> {
radio_button4.background = ContextCompat.getDrawable(this, drawableView)
}
}
}
I would really recommend just creating a data class like this:
data class QuestionOption(val question:String, val isCorrect = false)
Afterwards you can shuffle any way you like and just check if the selected QuestionOption has isCorrect set to true. You get a bunch of benefits and the logic gets simpler.
Edit:
To make it easier to declare questions this way:
In general if you add questions in your code you want only as much necessary code as required. For this you can either declare a good constructor or a function that basically maps your values to a constructor. In your case I'd say
data class Questions3(
val id: Int, val question: String, val option1: String, val option2: String,
val option3: String, val correctOption: String, val image: Int?=null,val dialogBox1: Int?=null,val dialogBox2: Int?=null)
(notice how the optional parameters are last, you don't need to specify them as well thanks to them beeing null by default)
Makes sense, in theory you could also (not too clean but easy) just shuffle option 1-3 & correctOption and then just compare if the correctOption String matches the selected String.
Otherwise as I said, you can always create logic for mapping stuff. Here you can either map from Constructor to another Constructor, same with Functions that return a finished Object.
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