Read CSV line-by-line in Kotlin - android

I'm writing a simple import application and need to read a CSV file, show result in a grid and show corrupted lines of the CSV file in another grid.
Is there any built-in lib for it or any easy pythonic-like way?
I'm doing it on android.

[Edit October 2019] A couple of months after I wrote this answer, Koyama Kenta wrote a Kotlin targeted library which can be found at https://github.com/doyaaaaaken/kotlin-csv and which looks much better to me than opencsv.
Example usage: (for more info see the github page mentioned)
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
fun main() {
csvReader().open("src/main/resources/test.csv") {
readAllAsSequence().forEach { row ->
//Do something
println(row) //[a, b, c]
}
}
}
For a complete minimal project with this example, see https://github.com/PHPirates/kotlin-csv-reader-example
Old answer using opencsv:
As suggested, it is convenient to use opencsv. Here is a somewhat minimal example:
// You can of course remove the .withCSVParser part if you use the default separator instead of ;
val csvReader = CSVReaderBuilder(FileReader("filename.csv"))
.withCSVParser(CSVParserBuilder().withSeparator(';').build())
.build()
// Maybe do something with the header if there is one
val header = csvReader.readNext()
// Read the rest
var line: Array<String>? = csvReader.readNext()
while (line != null) {
// Do something with the data
println(line[0])
line = csvReader.readNext()
}
As seen in the docs when you do not need to process every line separately you can get the result in the form of a Map:
import com.opencsv.CSVReaderHeaderAware
import java.io.FileReader
fun main() {
val reader = CSVReaderHeaderAware(FileReader("test.csv"))
val resultList = mutableListOf<Map<String, String>>()
var line = reader.readMap()
while (line != null) {
resultList.add(line)
line = reader.readMap()
}
println(resultList)
// Line 2, by column name
println(resultList[1]["my column name"])
}
Dependency for Gradle: compile 'com.opencsv:opencsv:4.6' or for Gradle Kotlin DSL: compile("com.opencsv:opencsv:4.6") (as always, check for latest version in docs).

In terms of easiness, kotlin written csv library is better.
For example, you can write code in DSL like way with below library that I created:
https://github.com/doyaaaaaken/kotlin-csv
csvReader().open("test.csv") {
readAllAsSequence().forEach { row ->
//Do something with the data
println(row)
}
}

Use opencsv.
This is gonna work like a charm for reading a CSV file.
As far as logging the corrupted lines is concerned you can do it using this logic.
while(input.hasNextLine())
{
try
{
//execute commands by reading them using input.nextLine()
}
catch (ex: UserDefinedException)
{
//catch/log the exceptions you're throwing
// log the corrupted line the continue to next iteration
}
}
Hope this helps.

I used net.sourceforge.javacsv with my Kotlin code for parsing CSV files. It is a "java" library but within kotlin it is fairly straightforward to work with it like
val reader = CsvReader("/path/to/file.csv").apply {
trimWhitespace = true
skipEmptyRecords = true
readHeaders()
}
while (reader.readRecord()) {
// do whatever
}

Frankly speaking, it is quite easy to make a simple reader in Kotlin using modern Java features, check this (REMEMBER to handle BOM :-)):
fun processLineByLine(csv: File, processor: (Map<String, String>) -> Unit) {
val BOM = "\uFEFF"
val header = csv.useLines { it.firstOrNull()?.replace(BOM, "")?.split(",") }
?: throw Exception("This file does not contain a valid header")
csv.useLines { linesSequence ->
linesSequence
.drop(1)
.map { it.split(",") }
.map { header.zip(it).toMap() }
.forEach(processor)
}
}
Than you can use it as follows (depends on your file structure):
processLineByLine(File("./my-file.csv")) { row ->
println("UserId: ${row["userId"]}")
println("Email: ${row["email"]}")
}

If you prefer to use your own data class for each row you should have a look at my solution https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/csv/CSVTable.kt
data class User(
val name: String,
val phone: String,
val email: String
) {
constructor(columns: List<String>) : this(
name = columns[0],
phone = columns[1],
email = columns[2]
)
}
CSVTable.print(FileInputStream("users.csv"))
val userList = CSVTable(FileInputStream("users.csv"), ::User).rows

I know i'm a bit late, but I recently had problems with parsing CSV and there seemed to be no library good enough for what I was looking for, so I created my own called Kotlin CSV stream.
This library is special because it doesn't throw exceptions on an invalid input, but returns in the result instead, which might be useful in some cases.
Here is an example of how easy it is to use
val reader = CsvReader()
.readerForType<CsvPerson>()
val people = reader.read(csv).map { it.getResultOrThrow() }.toList()

For version of commons-csv version 1.9.0, have implemented below code to get results.
It uses CSVBuilder and CSVFormat to get records with skip headers and auto-identify headers on basis of first row.
fun csvReader(file: MultipartFile): ResultListObject? {
var result = ResultListObject()
var csvFormat=CSVFormat.Builder.create().setHeader().setSkipHeaderRecord(true).build()
var csvRecords = CSVParser(file.inputStream.bufferedReader(), csvFormat)
csvRecords.forEach{csvRecords->
rowRecord.field1=records.get("field1")
rowRecord.field2=records.get("field2")
...
...
result.add(rowRecord)
}
return result
}

Related

How to use interface in swift with Kotlin multiplatform moblie

I am trying to use interface in swift, but it unable to find the property
commainMain
interface ApplicationToken {
val accessToken: String
val refreshToken: String
}
iosMain
Platform.kt
lateinit var tokenProvider: ApplicationToken
HttpClient.kt
actual fun httpClient(config: HttpClientConfig<*>.() -> Unit) = HttpClient(Darwin) {
config(this)
engine {
configureRequest {
setAllowsCellularAccess(true)
}
}
install(Auth) {
bearer {
loadTokens {
BearerTokens(tokenProvider.accessToken, "")
}
}
}
}
Now when I am to access tokenProvider in my swift code. It cannot find. I am adding image please have a look.
I tried another options to create class and implement my interface and call the class
class getToken : ApplicationToken {
let accessToken: String = ""
let refreshToken: String = ""
}
_ = getToken()
But it gives me the error. I cannot paste it because I don't understand in xcode.
When generating code for extensions (e.g. fun SomeClass.extension()) and for global variables, like in your case, Kotlin creates kind of a namespace, related to the filename.
In your case your property should be under PlatformKt.tokenProvider.
Usually when it's hard to find how your kotlin code is visible on iOS side, it's useful to check out framework header file. It should be places somewhere around here(replace shared with your framework name):
shared/build/cocoapods/framework/shared.framework/Headers/shared.h
I prefer adding this file by reference in my Xcode project, so I can have fast access all the time:
Then you can easily search through this file for your variable/class/etc name.

Koltin Flow flatMapLatest to combineTransform Using Multiple StateFlows

I have the below working code which uses a dropdown to update the satusFilterFlow to allow for the filtering of characters through the getCharacterList call. The getCharacterList call uses the jetpack paging and returns Flow<PagerData<Character>>.
private val statusFilterFlow = MutableStateFlow<StatusFilter>(NoStatusFilter)
// private val searchFilterFlow = MutableStateFlow<SearchFilter>(NoSearchFilter)
val listData: LiveData<PagingData<Character>> =
statusFilterFlow.flatMapLatest{ statusFilter ->
characterRepository.getCharacterList(null, statusFilter.status)
.cachedIn(viewModelScope)
.flowOn(Dispatchers.IO)
}.asLiveData()
Given the above working solution, what is the correct flow extension to allow for me to add multiple StateFlows as I build out additional filters (e.g. SearchFilter).
I have tried combineTransorm as follows:
private val statusFilterFlow = MutableStateFlow<StatusFilter>(NoStatusFilter)
private val searchFilterFlow = MutableStateFlow<SearchFilter>(NoSearchFilter)
val listData: LiveData<PagingData<Character>> =
statusFilterFlow.combineTransform(searchFilterFlow) { statusFilter, searchFilter ->
characterRepository.getCharacterList(searchFilter.search, statusFilter.status)
.flowOn(Dispatchers.IO)
.cachedIn(viewModelScope)
}.asLiveData()
However, this gives me a "Not enough information to infer type variable R" error.
The usual way to understand and/or fix those errors is to specify types explicitly in the function call:
statusFilterFlow.combineTransform<StatusFilter, SearchFilter, PagingData<Character>>(searchFilterFlow) { ... }
This is orthogonal to the problem at hand, but I'd also suggest using the top-level combineTransform overload that takes all source flows as argument (instead of having the first one as receiver), so there is a better symmetry. Since I believe there is no reason one of the filters is more special than the other.
All in all, this gives:
val listData: LiveData<PagingData<Character>> =
combineTransform<StatusFilter, SearchFilter, PagingData<Character>>(statusFilterFlow, searchFilterFlow) { statusFilter, searchFilter ->
characterRepository.getCharacterList(searchFilter.search, statusFilter.status)
.flowOn(Dispatchers.IO)
.cachedIn(viewModelScope)
}.asLiveData()
For anymore else, this is too complex or doesn't work out for you ... Use Combine then flatMap latest on the top of that.
private val _selectionLocation: MutableStateFlow<Location?> = MutableStateFlow(null)
val searchKeyword: MutableStateFlow<String> = MutableStateFlow("")
val unassignedJobs: LiveData<List<Job>> =
combine(_selectionLocation, searchKeyword) { location: Location?, keyword: String ->
Log.e("HomeViewModel", "$location -- $keyword")
location to keyword
}.flatMapLatest { pair ->
_repo.getJob(Status.UNASSIGNED, pair.first).map {
Log.e("HomeViewModel", "size ${it.size}")
it.filter { it.desc.contains(pair.second) }
}
}.flowOn(Dispatchers.IO).asLiveData(Dispatchers.Main)

How to combine two kotlin data objects with shared variable into one object?

I'm making a little application which tracks cryptocurrency values at the Bittrex exchange.
For this I'm using Bittrex' public api (https://bittrex.github.io/api/v3)
Unfortunately the api doesn't provide the data I want with just one call, therefore I need to do two api calls.
What I want to achieve is to have one object containing all of the following values:
symbol (This is a shared value between both api calls, so this needs
to match)
quoteVolume
percentChange
lastTradeRate
The bold variable is part of one api call, the other values are part of the other. 'Symbol' is part of both.
I'm using kotlin coroutines and I was hoping that I don't have to use something like RxJava to get this to work.
CoroutineScope(IO).launch {
val tickers = async {
api.getTickers()
}.await()
val markets = async {
api.getMarkets()
}.await()
val result = mutableListOf<Market>()
for (ticker in tickers.data) {
for (market in markets.data) {
if (ticker.symbol == market.symbol) {
result.add(
Market(
ticker.symbol,
ticker.lastTradeRate,
market.quoteVolume,
market.percentChange
)
)
}
}
}
}
You can make the 2 calls in parallel using coroutines.
Assuming firstApi and secondApi are suspend functions that return the data for each of the 2 blobs of info you need,
val data1Deferred = async { firstApi() }
val data2Deferred = async { secondApi() }
val data1 = data1Deferred.await()
val data2 = data2Deferred.await()
val result = Result(
// build result from data1 and data2
)
You would also need to add error handling.
Edit:
you can group your list by the symbol and generate a map:
val marketMap = markets.associateBy { it.symbol }
Then for each ticker you can get the corresponding market
for (ticker in tickers) {
val market = marketMap[ticker.symbol]
if (market != null) {
// join ticker and market
}
}

Find a line in an .ics file using Kotlin

I'm trying to store certain lines from an .ics file to separate strings depending on their contents. I've been able to convert an .ics file to a string, but I am having difficulty searching it line by line to find certain keywords.
The string (and file) contains:
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//School of Rochester NY |-ECPv4.8.1//NONSGML v1.0//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:School of Rochester NY |
I've been able to display the text in the logcat, but I have not been able to save the lines as separate strings.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var textView = findViewById<TextView>(R.id.textView)
val file_name = "education-1e1a4bdab8e.ics"
val ics_string = application.assets.open(file_name).bufferedReader().use {
it.readText()
}
Log.i("TAG", ics_string)
textView.text = ics_string
if (ics_string.contains("BEGIN:VCALENDAR", ignoreCase = true))
{
Log.i("TAG", "contains event")
}
}
The logcat confirms that the text is in the document, but not which line.
Is there any way to add lines of a text as separate strings?
Just looking at the BufferedReader you have already 4 functions that all give you the lines.
readLines gives you a List<String> containing all the lines
useLines lets you use a sequence of lines which you can then transform and assures that after calling it, the reader is closed
lineSequence() returns a sequence of the lines, but does not close the reader after calling it
lines() returns a Stream<String> containing the lines and basically comes from the BufferedReader itself. As you are using Kotlin you probably do not want to use this method.
useLines and readLines are also available on File itself
As I am not sure what you really want to accomplish I recommend you start with readLines directly. The ics-file is usually rather small and with the lines you can still filter/map whatever you want. The next best candidate then is probably either useLines or lineSequence. It really depends on what you do next.
You can use the extension function on String, lines(), something like this:
fun lines() {
val text = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//School of Rochester NY |-ECPv4.8.1//NONSGML v1.0//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:School of Rochester NY |"""
val foundLines = text.lines().map {
it.contains("BEGIN:VCALENDAR") to it
}.filter { (contains, _) -> contains }
.map { it.second }
println(foundLines)
}
fun main() {
lines()
}

Unresolved referance "firebase" when trying to call TIMESTAMP, while all other services work

I am trying to create a time stamp using the firebase server value.
In the documentation in says to use
firebase.database.ServerValue.TIMESTAMP
*edit: before I didn't realize I was looking at the JS documentation. I've relinked it to the Android ones though they don't seem to specify a format there. I'm still looking.
But it return unresolved referance "firebase"
I've looked at other similar question, and they offered other formats like Firebase.ServerValue.TIMESTAMP and i've tried them but they don't work either and I think they're outdated.
My other firebase services are working just fine (Authorization, Database & Storage) so I can't understand why am I getting this error.
I am trying o achive that to create a simple timestamp the I will later, after pulled from the server, would convert to a nicer format with PrettyTime.
This is the part of the code the is giving me the error:
class NewQuestionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_question)
val questionTitle : EditText = findViewById(R.id.new_question_title)
val questionDetails = findViewById<EditText>(R.id.new_question_details)
val questionTags = findViewById<EditText>(R.id.new_question_tags)
val questionButton = findViewById<Button>(R.id.new_question_btn)
questionButton.setOnClickListener {
postQuestion(questionTitle.text.toString(), questionDetails.text.toString(), questionTags.text.toString(), firebase.database.ServerValue.TIMESTAMP)
}
}
private fun postQuestion(title : String, details : String, tags : String, timestamp : String) {
val uid = FirebaseAuth.getInstance().uid
val ref = FirebaseDatabase.getInstance().getReference("/questions").push()
val newQuestion = Question(title, details, tags, timestamp)
ref.setValue(newQuestion)
.addOnSuccessListener {
Log.d("postQuestionActivity", "Saved question to Firebase Database")
}.addOnFailureListener {
Log.d("postQuestionActivity", "Failed to save question to database")
}
}
}
It is very odd a class start with lower case. I guess the most suitable approach is to allow the IDE to make the import for you, try with
ServerValue.TIMESTAMP

Categories

Resources