How to add items to MutableLiveData arraylist? - kotlin

I have a MainViewModelwith the code:
private val locationList: MutableLiveData<ArrayList<Location>> = MutableLiveData()
fun getLocationList(): MutableLiveData<ArrayList<Location>> = locationList
and a fragment where I am trying to add values to the arraylist, but I don't know how:
mainViewModel.getLocationList.value = arrayListof(location) //creates always a new list
Maybe someone can help me.
Thank you

mainViewModel.getLocationList.value?.add(newLocation)
mainViewModel.getLocationList.value = mainViewModel.getLocationList.value // notify observers

Related

Kotlin Cancels getParceableExtra call when passing data class using Intent

Trying to pass a data class User from one Activity to another using Intent.
My putExtra looks like this using my observe fun:
val intent = Intent(this, MainActivity::class.java)
intent.putExtra("userData",userData)
startActivity(intent)
My get routine looks like this:
userData = intent.getParcelableExtra<User>("userData") as User
or
userData = intent.getParcelableExtra("userData")
My problem is that Android Studio strikes out the function. My User data class is marked #Parcelize. It all ends up getParcelableExtra.
I've add to my gradle build:
id 'kotlin-parcelize'
I've read several posts about Parcelable being more modern than Serialable, so that's the technique I'm using. All the posts are from 2018 or prior and many of them in Java.
How does one send an data class from one Activity to another using Intent?
Since getParcelableExtra (String name) is deprecated from api level 33 you can use getParcelableExtra (String name, Class<T> clazz) from api level 33
In Your case use :
val userData =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra("userData", userData::class.java)
}
else{
intent.getParcelableExtra("userData") as userData?
}
where TIRAMISU is constant value 33
To get more info:Read this:
https://developer.android.com/reference/android/content/Intent#getParcelableExtra(java.lang.String,%20java.lang.Class%3CT%3E)
SOLUTION:
Given the need for compiler version 33 for the most modern solution, I went with a more backward compatible solution. This code translates the data object into a string, then back into the object.
SETUP: (PUT)
private var _userData = MutableLiveData<User>() // does the username and pw validate?
val userData : LiveData<User>
get() = _userData
SETUP: (GET)
private lateinit var userData: User
PUT CODE:
val intent = Intent(this, MainActivity::class.java)
val jsonUserData = Gson().toJson(userData)
intent.putExtra("userData",jsonUserData)
GET CODE:
val jsonUserData = intent.getStringExtra("userData")
userData = Gson().fromJson(jsonUserData, User::class.java)

Jetpack compose and Kotlin, dynamic UI losing values on recomp

I am making a dynamic UI using kotlin and Jetpack compose and storing the information in an object box database.
The aim is that i will have a composable that starts off with 1 initial item that is empty and when the contents of the textbox have been filled in would allow the red "+" button to be clicked and then another textfield would appear. These values will need to be able to be edited constantly all the way until the final composable value is stored. The button changes colour currently and the states are fine with the button so i can add and remove rows
The data comes in as a string and is converted into a Hashmap<Int, String>. The int is used to store the position in the map being edited and the string would be the text value.
Using log messages i see that the information is updated in the list and for recomp sake i instantly store the value of the edited list in a converted json string.
At the moment:
When i scroll past the composable it resets and looks like the initial state (even if i have added multiple rows)
Log messages show that my hashmap has the values from before e.g. {"0":"asdfdsa"} but the previous positions are ignored and as the previous information would still be present but not shown on the UI when i enter it into the first field again (the others are not visible at the time) {"0":"asdfdsa","0":"hello"}. This would later cause an error when trying to save new data to the list because of the duplicate key
In the composables my hashmap is called textFields and is defined like this. Number is used to determine how many textfields to draw on the screen
val textFields = remember { getDataStringToMap(data.dataItem.dataValue) }
val number = remember { mutableStateOf(textFields.size) }
the method to getDataStringToMap is created like this
private fun getDataMapToString(textFieldsMap: HashMap<Int, String>): String {
val gson = Gson()
val newMap = hashMapOf<Int, String>()
for (value in textFieldsMap){
if (value.value .isNotBlank()){
newMap[value.key] = value.value
}
}
return gson.toJson(newMap)
}
and the method to getDataStringToMap is created like this (I explicitly define the empty hashmap type because its more readable for me if i can see it)
private fun getDataStringToMap(textsFieldsString: String): HashMap<Int, String> {
val gson = Gson()
return if (textsFieldsString.isBlank()) {
hashMapOf<Int, String>(0 to "")
} else {
val mapType = HashMap<Int, String>().javaClass
gson.fromJson(textsFieldsString, mapType)
}
the composables for the textfields are called like this
items(number.value) { index ->
listItem(
itemValue = textFields[index].orEmpty(),
changeValue = {
textFields[index] = it
setDataValue(getDataMapToString(textFields))
},
addItem = {
columnHeight.value += itemHeight
scope.launch {
scrollState.animateScrollBy(itemHeight)
}
},
deleteItem = {
columnHeight.value -= itemHeight
scope.launch {
scrollState.animateScrollBy(-itemHeight)
}
},
lastItem = index == number.value - 1,
index = index
)
}
Edited 30/12/2022
Answer from #Arthur Kasparian solved issues. Change to rememberSaveable retains the UiState even on scroll and recomp.
Now just to sort out which specific elements are removed and shown after :D
The problem is that remember alone does not save values on configuration changes, whereas rememberSaveable does.
You can read more about this here.

Kotlin - assign value inside while to use it outside

I'm very new in kotlin and wanted to solve following problem with a do while:
I want to create a hash and want to check if there is the same hash stored in a key-value store as a key.
In java I would make it with a String variable which I declared outside the while. But that will only work with a var in Kotlin and I learned that it is common practise to avoid var.
My code looks as following (with var...)
var hash = ""
do {
hash = createHash(longUrl)
val optional = shortUrlRepository.findById(hash)
} while(optional.isPresent)
What would you say is the best way to solve this?
thank you a lot!
Maybe something like this?
val hash = generateSequence { createHash(longUrl) }
.first { !shortUrlRepository.findById(it).isPresent }
... and of course, you can always localize var and pass it outside as val.
val someVal = run {
var someVar: String = ""
// do super logic with var
someVar
}
...

how to add Array index value in Kotlin?

first, I create empty Array(Kotlin) instance in companion object.
companion object {
var strarray: Array<String> = arrayOf()
var objectarray: LinkedHashMap<Int, List<Any>> = LinkedHashMap<Int, List<Any>>()
}
and I expected that I use empty array instance when read textString from CSV File.
fun csvFileToString():String {
val inputStream = File(Paths.get("").toAbsolutePath().toString()
.plus("/src/main/SampleCSVFile_2kb.csv")).inputStream()
val reader = inputStream.bufferedReader()
var iterator = reader.lineSequence().iterator()
var index:Int = 1;
while (iterator.hasNext()){
var lineText:String = iterator.next()
strarray.set(index, lineText)
index++
}
return ""
}
but when I run that source code
a.csvFileToString()
println(CsvParser.strarray)
occured exception
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
strarray.set(index, lineText) <<<<<<<<< because of this line
can I use Array(from kotlin collection) like ArrayList(from java collection)?
You can add a new item to an array using +=, for example: item += item
private var songs: Array<String> = arrayOf()
fun add(input: String) {
songs += input
}
Size of Array is defined at its creation and cannot be modified - in your example it equals 0.
If you want to create Array with dynamic size you should use ArrayList.
arrayOf gives you an array. Arrays have fixed length even in Java.
listOf gives you an immutable list. You cannot add or remove items in this list.
What you're looking for is mutableListOf<String>.
In your current approach, reusing a member property, don't forget to clear the list before every use.
Your code can be further simplified (and improved) like so:
out.clear()
inputStream.bufferedReader().use { reader -> // Use takes care of closing reader.
val lines = reader.lineSequence()
out.addAll(lines) // MutableList can add all from sequence.
}
Now imagine you wanted to consume the output list but needed to parse another file at the same time.
Consider working towards a pure function (no side effects, for now no accessing member properties) and simplifying it even further:
fun csvFileToString(): String { // Now method returns something useful.
val inputStream = File(Paths.get("").toAbsolutePath().toString()
.plus("/src/main/SampleCSVFile_2kb.csv")).inputStream()
inputStream.bufferedReader().use {
return it.lineSequence().joinToString("\n")
}
}
In this case we can totally skip the lists and arrays and just read the text:
inputStream.bufferedReader().use {
return it.readText()
}
I'm assuming that's what you wanted in the first place.
Kotlin has a lot of useful extension functions built-in. Look for them first.

How do I convert a List(Of T) to an ObservableCollection(Of T) in VB.NET?

Is there a way to do this without iterating through the List and adding the items to the ObservableCollection?
No, there is no way to directly convert the list to an observable collection. You must add each item to the collection. However, below is a shortcut to allow the framework to enumerate the values and add them for you.
Dim list as new List(of string)
...some stuff to fill the list...
Dim observable as new ObservableCollection(of string)(list)
I'm late but I want to share this interesting piece for converting a list into a ObservableCollection if you need a loop:
public static ObservableCollection<T> ToObservableCollection<T>(this IEnumerable<T> coll)
{
var c = new ObservableCollection<T>();
foreach (var e in coll) c.Add(e);
return c;
}
You could pass an collection to the ObservableCollection constructor:
List<Product> myProds = ......
ObservableCollection<Product> oc = new ObservableCollection<Product>(myProds);
Now you have to translate these to VB.NET :)
//Create an observable collection TObservable.
ObservableCollection (TObservable) =new ObservableCollection (TObservable)();
//Convert List items(OldListItems) to collection
OldListItems.ForEach(x => TObservable.Add(x));
to clarify what Junior is saying (with an added example if you're using LINQ that returns an IEnumerable):
//Applications is an Observable Collection of Application in this example
List<Application> filteredApplications =
(Applications.Where( i => i.someBooleanDetail )).ToList();
Applications = new ObservableCollection<Application>( filteredApplications );
Even though I'm late, I wanna share a quick enhancement to Junior's answer: let the developer define the converter function used to convert observable collection objects from the source collection to the destination one.
Like the following:
public static ObservableCollection<TDest> ToObservableCollection<TDest, TSource>(this IEnumerable<TSource> coll, Func<TSource, TDest> converter)
{
var c = new ObservableCollection<TDest>();
foreach (var e in coll)
{
c.Add(converter(e));
}
return c;
}
ObservableCollection<yourobjectname> result = new ObservableCollection<yourobjectname>(yourobjectlist);