Home > Software design >  Kotlin How to use CSV Functions
Kotlin How to use CSV Functions

Time:06-12

Thanks for great help from Tenfour04, I got wonderful code of hundling CSV files. Kotlin How to read and write CSV file

However, I am in trouble like followings.

[1] How to call these functions.

[2] How to initialize 2 dimensions array variables.

Below is the code that finally worked.

MainActivity.kt

package com.surlofia.csv_tenfour04_1

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import java.io.File
import java.io.IOException
import com.surlofia.csv_tenfour04_1.databinding.ActivityMainBinding

var chk_Q_Num: MutableList<Int> = mutableListOf  (
    0,
    1, 2, 3, 4, 5,
    6, 7, 8, 9, 10,
    11, 12, 13, 14, 15,
    16, 17, 18, 19, 20,
)

var chk_Q_State: MutableList<String> = mutableListOf  (
    "z",
    "a", "b", "c", "d", "e",
    "f", "g", "h", "i", "j"
    )


class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // setContentView(R.layout.activity_main)

        binding = ActivityMainBinding.inflate(layoutInflater)

        val view = binding.root

        setContentView(view)

        // Load saved data at game startup. It will be invalid if performed by other activities.

        val filePath = filesDir.path   "/chk_Q.csv"
        val file = File(filePath)

        binding.fileExists.text = isFileExists(file).toString()

        if (isFileExists(file)) {
            val csvIN = file.readAsCSV()

            for (i in 0 .. 10) {
                chk_Q_Num[i] = csvIN[i][0].toInt()
                chk_Q_State[i] = csvIN[i][1]
            }

        }

        // Game Program Run


        val csvOUT = mutableListOf(
            mutableListOf("0","OK"),
            mutableListOf("1","OK"),
            mutableListOf("2","OK"),
            mutableListOf("3","Not yet"),
            mutableListOf("4","Not yet"),
            mutableListOf("5","Not yet"),
            mutableListOf("6","Not yet"),
            mutableListOf("7","Not yet"),
            mutableListOf("8","Not yet"),
            mutableListOf("9","Not yet"),
            mutableListOf("10","Not yet")
        )

        var tempString = ""

        for (i in 0 .. 10) {
            csvOUT[i][0] = chk_Q_Num[i].toString()
            csvOUT[i][1] = "OK"

            tempString = tempString   csvOUT[i][0]   "-->"   csvOUT[i][1]   "\n"
        }

        binding.readFile.text = tempString

        // and save Data
            file.writeAsCSV(csvOUT)


    }


    // https://www.techiedelight.com/ja/check-if-a-file-exists-in-kotlin/

    private fun isFileExists(file: File): Boolean {
        return file.exists() && !file.isDirectory
    }

    @Throws(IOException::class)
    fun File.readAsCSV(): List<List<String>> {
        val splitLines = mutableListOf<List<String>>()
        forEachLine {
            splitLines  = it.split(", ")
        }
        return splitLines
    }

    @Throws(IOException::class)
    fun File.writeAsCSV(values: List<List<String>>) {
        val csv = values.joinToString("\n") { line -> line.joinToString(", ") }
        writeText(csv)
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@ id/fileExists"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Hello World!"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@ id/readFile"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="TextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@ id/fileExists" />

</androidx.constraintlayout.widget.ConstraintLayout>

chk_Q.csv

0,0
1,OK
2,OK
3,Not yet
4,Not yet
5,Not yet
6,Not yet
7,Not yet
8,Not yet
9,Not yet
10,Not yet

build.gradle(:app)

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

android {
    compileSdk 33
    buildToolsVersion '33.0.0'

    defaultConfig {
        applicationId "com.surlofia.csv_tenfour04_1"
        minSdk 23
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }

    buildFeatures {
        viewBinding true
    }

}

dependencies {

    implementation 'androidx.core:core-ktx:1.8.0'
    implementation 'androidx.appcompat:appcompat:1.4.2'
    implementation 'com.google.android.material:material:1.6.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

[1] How to call these functions.

[1] read

    if (isFileExists(file)) {
        val csvIN = file.readAsCSV()

        for (i in 0 .. 10) {
            chk_Q_Num[i] = csvIN[i][0].toInt()
            chk_Q_State[i] = csvIN[i][1]
        }

    }

[2] write

        file.writeAsCSV(csvOUT)

Above code seems work well. Did I call these funtions in right way? Or there are better codes than above ?

[2] How to initialize 2 dimensions array variables

    val csvOUT = mutableListOf(
        mutableListOf("0","OK"),
        mutableListOf("1","OK"),
        mutableListOf("2","OK"),
        mutableListOf("3","Not yet"),
        mutableListOf("4","Not yet"),
        mutableListOf("5","Not yet"),
        mutableListOf("6","Not yet"),
        mutableListOf("7","Not yet"),
        mutableListOf("8","Not yet"),
        mutableListOf("9","Not yet"),
        mutableListOf("10","Not yet")
    )

I would like to know the clever way to use a for loop instead of writing specific values one by one.

For Example, like bellow.

    val csvOUT = mutableListOf(mutableListOf())
    for (i in 0 .. 10) {
        csvOUT[i][0] = i
        csvOUT[i][1] = "OK"
    }

And Error message. Not enough information to infer type variable T

It would be great if you could provide an example of how to execute this for beginners.

CodePudding user response:

In my opinion there are basically two parts to your question. First you need an understanding of the Kotlin type system including generics. Secondly you want some knowledge about approaches to the problem at hand.

type-system and generics

The function mutableListOf you're using is generic and thus needs a single type parameter T, as can be seen by definition its taken from the documentation:

fun <T> mutableListOf(): MutableList<T>

Most of the time the Kotlin compiler is quite good at type-inference, that is guessing the type used based on the context. For example, I do not need to provide a type explicitly in the following example, because the Kotlin compiler can infer the type from the usage context.

val listWithInts = mutableListOf(3, 7)

The infered type is MutableList<Int>. However, sometimes this might not be what one desires. For example, I might want to allow null values in my list above. To achieve this, I have to tell the compiler that it should not only allow Int values to the list but also null values, widening the type from Int to Int?. I can achieve this in at least two ways.

  1. providing a generic type parameter
val listWithNullableInts = mutableListOf<Int?>(3, 7)
  1. defining the expected return type explicitly
val listWithNullableInts: MutableList<Int?> = mutableListOf(3, 7)

In your case the compiler does NOT have enough information to infer the type from the usage context. Thus you either have to provide it that context, e.g. by passing values of a specific type to the function or using one of the two options named above.

initialization of multidimensional arrays

There are questions and answers on creating multi-dimensional arrays in Kotlin on StackOverflow already.

One solution to your problem at hand might be the following.

val csvOUT: MutableList<MutableList<String>> = mutableListOf(mutableListOf())

for (i in 0 .. 10) {
    csvOUT[i][0] = "$i"
    csvOUT[i][1] = "OK"
}

You help the Kotlin compiler by defining the expected return type explicitly and then add the values as Strings to your 2D list.

If the dimensions are fixed, you might want to use fixed-size Arrays instead.

val csvArray = Array(11) { index -> arrayOf("$index", "OK") }

In both solutions you convert the Int index to a String however. If the only information you want to store for each level is a String, you might as well use a simple List<String and use the index of each entry as the level number, e.g.:

val csvOut = List(11) { "OK" }
val levelThree = csvOut[2] // first index of List is 0 

This would also work with more complicated data structures instead of Strings. You simply would have to adjust your fun File.writeAsCSV(values: List<List<String>>) to accept a different type as the values parameter. Assume a simple data class you might end up with something along the lines of:

data class LevelState(val state: String, val timeBeaten: Instant?)

val levelState = List(11) { LevelState("OK", Instant.now()) }

fun File.writeAsCSV(values: List<LevelState>) {
    val csvString = values
        .mapIndexed { index, levelState -> "$index, ${levelState.state}, ${levelState.timeBeaten}" }
        .joinToString("\n")
    
    writeText(csvString)
}
  • Related