I am trying to write a program in Kotlin for the first time, and I'm banging my head against my own inexperience. The problem I'm running up against would be the work of a moment in Objective C (because I have experience of that language) but Kotlin is proving trickier.
I need to be able to save the contents of a table (of varying length - the user can both add and delete rows), and then load it again when the program is relaunched. I'm able to save the table contents as follows…
fun saveTableData(context: Context, tableLayout: TableLayout) {
val sharedPreferences = context.getSharedPreferences("TableData", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
val rowCount = tableLayout.childCount
editor.clear()
println("Saving $rowCount rows")
for (i in 1 until rowCount) { // 1 because we omit the header row
val tableRow = tableLayout.getChildAt(i) as TableRow
val colCount = tableRow.childCount
for (j in 0 until colCount - 1) {
val textView = tableRow.getChildAt(j) as TextView
val key = "row_${i}_col_${j}"
val value = textView.text.toString()
println("$key, $value")
editor.putString(key, value)
}
}
editor.apply()
}
The resultant saved XML looks correct…
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="row_1_col_2"></string>
<string name="row_2_col_0">Clark</string>
<string name="row_2_col_3"></string>
<string name="row_2_col_1">Kent</string>
<string name="row_2_col_2"></string>
<string name="row_1_col_1">Wade</string>
<string name="row_1_col_0">Wilson</string>
</map>
When I try to reload the file, it a) loads too many rows (three, instead of two) and the rows all say "Clark Kent"
fun loadTableData(context: Context, tableLayout: TableLayout) {
val tableDataXml: String = xmlHelper.getSharedPreferencesRawXml(context, "TableData")
if (tableDataXml.isNullOrEmpty()) { return }
val xmlArray = xmlHelper.parseXML(tableDataXml)
val arraycount = xmlArray.count()
println("Loading $arraycount rows")
tableLayout.removeAllViews()
for (row in xmlArray) {
val tableRow = TableRow(context)
for (cell in row) {
val textView = TextView(context)
textView.text = cell ?: ""
textView.setPadding(16, 16, 16, 16)
tableRow.addView(textView)
}
tableLayout.addView(tableRow)
}
}
with the helper class defined as follows…
class XmlHelperFunctions {
fun <T> addToArray(originalArray: Array<Array<T>>, newElement: Array<T>): Array<Array<T>> {
@Suppress("UNCHECKED_CAST")
val newArray = java.lang.reflect.Array.newInstance(
originalArray::class.javaponentType!!,
originalArray.size + 1
) as Array<Array<T>>
for (i in originalArray.indices) {
newArray[i] = originalArray[i]
}
newArray[originalArray.size] = newElement
return newArray
}
fun parseXML(xmlData: String?): Array<Array<String?>> {
val parser = Xml.newPullParser()
parser.setInput(xmlData?.reader() ?: "".reader())
var eventType = parser.eventType
val emptyRow = arrayOfNulls<String>(4) // this will need to be updated if the number of data fields to be stored increases
var table: Array<Array<String?>> = arrayOf(emptyRow)
val seenRows = mutableSetOf<Int>() //XML is not necessarily in the right order
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG && parser.name == "string") {
val name = parser.getAttributeValue(null, "name")
val value = parser.nextText()
val row = name.substringAfter("row_").substringBefore("_col_").toInt()
val col = name.substringAfter("col_").toInt()
if (!seenRows.contains(row)) {
println("row: $row")
table = addToArray(table, emptyRow) // the row number changed - so add a row
seenRows.add(row)
}
table[row][col] = value
}
try {
eventType = parser.next()
println("Parsing next")
} catch (e: Exception) {
println("ParseXML: Failed trying to parse.next")
return table
}
}
return table
}
fun getSharedPreferencesRawXml(context: Context, sharedPreferencesName: String): String {
// Locate the SharedPreferences XML file
val prefsFile = File(context.filesDir.parent, "shared_prefs/$sharedPreferencesName.xml")
// Read the file contents as text
return if (prefsFile.exists()) {
prefsFile.readText()
} else {
"" // need to return an empty string, bail early.
}
}
}
If anyone knows ObjC then all I'm trying to do is the equivalent of
//Save, where array is the array populating the NSTable
[[NSUserDefaults standardUserDefaults] setObject:array forKey:@"SavedArray"];
[[NSUserDefaults standardUserDefaults] synchronize];
//Restore
NSArray *reloadedArray = [[NSUserDefaults standardUserDefaults] objectForKey:@"SavedArray"];
NSMutableArray *mutableReloadedArray = [NSMutableArray arrayWithArray:reloadedArray];