how to add Array index value in Kotlin? - 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.

Related

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
}
...

Passing Multiple Mime Types to ActivityResultLauncher.launch()

I have following code
val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
//Some code here..
}
and somewhere else ,
getContent.launch("application/vnd.openxmlformats-officedocument.wordprocessingml.document")
I can successfully select the docx file . I need to select either pdf or doc or text or docx rather just to be able to select one kind(here docx).
I would recommend using OpenDocument instead of GetContent.
val documentPick =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
// do something
}
While launching the intent just add the mime types you want to get
documentPick.launch(
arrayOf(
"application/pdf",
"application/msword",
"application/ms-doc",
"application/doc",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"text/plain"
)
)
Using array doesn't work in my case. Instead, the following worked correctly.
Here custom class of ActivityResultContracts.GetContent is used. fun "createIntent" is overrided to customize method to make intent from the input.
// custom class of GetContent: input string has multiple mime types divided by ";"
// Here multiple mime type are divided and stored in array to pass to putExtra.
// super.createIntent creates ordinary intent, then add the extra.
class GetContentWithMultiFilter:ActivityResultContracts.GetContent() {
override fun createIntent(context:Context, input:String):Intent {
val inputArray = input.split(";").toTypedArray()
val myIntent = super.createIntent(context, "*/*")
myIntent.putExtra(Intent.EXTRA_MIME_TYPES, inputArray)
return myIntent
}
}
// define ActivityResultLauncher to launch : using custom GetContent
val getContent=registerForActivityResult(GetContentWithMultiFilter()){uri ->
... // do something
}
// launch it
// multiple mime types are separated by ";".
val inputString="audio/mpeg;audio/x-wav;audio/wav"
getContent.launch(inputString)

How I can remove\delete object in kotlin?

I have class and two objects. I want to delete 1st object. How I can delete it?
I tried just delete() (I found it on kotlinlangcom) but it doesn't work. I have red light bulb what recommend: "Create member function Person.delete", "Rename reference" and "Create extension function Person.delete".
fun main() {
// copy object in object
data class Person (var name: String = "Orig", var type: String = "piece",
var age: Int = 18, var high: Double = 25.7, var code: Int = 1522)
{
var info: String = "0"
get() = "Name: $name Age: $age Type: $type High: $high Code: $code"
}
val ann: Person = Person("Ann", "man", 10, 0.5, 1408) // 1st object with some properties
var bob: Person = Person("Bob", "girl", 20, 15.0, 1239) // 2nd object without prop
println(ann.info)// props 1st object
println(bob.info)// props 2nd object
print(" ---- ")
bob = ann.copy() // copy 1st in 2nd
println("Bob has Anns' props: ")
print("final " + bob.info) // new props 2nd object
bob.delete()
}
You don't need to thing about deleting objects like in other languages like c++/c ... the garbage collector of the JVM is taking care of it (if you use kotlin with jvm)
All you need to know is keeping no references on the object
So if you have a collection (list,map ...) where you put the object in, you also have to put it out if the collection is a property of a long living class like a model or something ... thats the only possiblity to getting into trouble within kotlin, putting a reference into a collection which is referenced by a static or long living object.
Within a function there is no need to delete the objects created withing.
Keep in mind that the garbage collector (GC) is not running instantly after finishing the method. There are different strategies depending on the age of the object and the garbage collector itself. If you would like to see the GC in action, this tool (visualgc) https://www.oracle.com/technetwork/java/visualgc-136680.html has some pretty nice visualisations.
You could also find much more details about garbage collection here: https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

Use filtered dataProvider contents when FileDownloader is called in Vaadin

I'm trying to download a csv file after applying filters to the DataProvider.
For some reason the filtered results are shown in the Grid, but the downloaded csv file still contains all data.
#AutoView
class FinancialTransactionsView : VerticalLayout(), View {
private val grid: Grid<FinancialTransaction>
private val yearField: ComboBox<Int>
private val dataProvider = DataProvider.ofCollection(FinancialTransaction.findAll())
private val fileDownloader: FileDownloader
init {
label("Financial Transactions") {
styleName = ValoTheme.LABEL_H1
}
yearField = comboBox("Select Year") {
setItems(listOf(2016, 2017, 2018))
addSelectionListener {
// Filter the data based on the selected year
if (it.value != it.oldValue) setDataProvider()
}
}
// Create FileDownloader and initialize with all contents in the DataProvider
fileDownloader = FileDownloader(createCsvResource())
val downloadButton = button("Download csv") {
styleName = ValoTheme.BUTTON_PRIMARY
onLeftClick {
// The idea here is to assign values from the filtered DataProvider to the FileDownloader
fileDownloader.fileDownloadResource = createCsvResource()
}
}
fileDownloader.extend(downloadButton)
fileDownloader.fileDownloadResource = createCsvResource()
grid = grid(dataProvider = dataProvider) {
expandRatio = 1f
setSizeFull()
addColumnFor(FinancialTransaction::companyId)
addColumnFor(FinancialTransaction::fiscalYear)
addColumnFor(FinancialTransaction::fiscalPeriod)
addColumnFor(FinancialTransaction::currency)
addColumnFor(FinancialTransaction::finalizedDebitAmountInCurrency)
addColumnFor(FinancialTransaction::finalizedCreditAmountInCurrency)
appendHeaderRow().generateFilterComponents(this, FinancialTransaction::class)
}
}
private fun createCsvResource(): StreamResource {
return StreamResource(StreamResource.StreamSource {
val csv = dataProvider.items.toList().toCsv()
try {
return#StreamSource csv.byteInputStream()
} catch (e: IOException) {
e.printStackTrace()
return#StreamSource null
}
}, "financial_transactions.csv")
}
private fun setDataProvider() {
dataProvider.clearFilters()
if (!yearField.isEmpty)
dataProvider.setFilterByValue(FinancialTransaction::fiscalYear, yearField.value)
}
}
toCsv() is an extension function List<FinancialTransaction> which returns a string containing csv data.
What can I do to get the filtered results in my csv file?
val csv = dataProvider.items.toList().toCsv()
I am not Kotlin guy, but I assume dataProvider.items is a shorthand to dataProvider.getItems() in Java, i.e. this method (and you use ListDataProvider)
https://vaadin.com/download/release/8.4/8.4.1/docs/api/com/vaadin/data/provider/ListDataProvider.html#getItems--
In Vaadin getItems() returns all items by passing all filters.
So instead you should do either of the following
dataProvider.fetch(..)
https://vaadin.com/download/release/8.4/8.4.1/docs/api/com/vaadin/data/provider/DataProvider.html#fetch-com.vaadin.data.provider.Query-
Where you give the filters you want to apply in the query, or
grid.getDataCommunicator.fetchItemsWithRange(..)
https://vaadin.com/download/release/8.4/8.4.1/docs/api/com/vaadin/data/provider/DataCommunicator.html#fetchItemsWithRange-int-int-
Which returns list of items with filters you have set applied, which I think is ideal for you
Thank you for using Vaadin-on-Kotlin!
I've just updated the Databases Guide which should hopefully answer all of your questions. If not, just let me know and I'll update the guides accordingly.
The ListDataProvider.items will not apply any filters and will always return all items.
You need to use the getAll() extension function in order to obey the filters set by the Grid.
This is now explained in the Exporting data from DataProviders chapter of the Databases Guide.
In your code, both the grid and the yearField will set the filter to the same data provider,
thus overwriting values set by each other. Please read the Chaining Data Providers chapter in the Databases Guide to learn how to AND multiple filters set by multiple components.
When you use private val dataProvider = DataProvider.ofCollection(FinancialTransaction.findAll()), that will load all transactions from the database in-memory. You can use a more memory-efficient way: private val dataProvider = FinancialTransaction.dataProvider (given that FinancialTransaction is an Entity)
Please let me know if this answers your questions. Thanks!