70

I can't seem to find this question yet, but what is the simplest, most-idiomatic way of opening/creating a file, writing to it, and then closing it? Looking at the kotlin.io reference and the Java documentation I managed to get this:

fun write() {
    val writer = PrintWriter("file.txt")  // java.io.PrintWriter

    for ((member, originalInput) in history) {  // history: Map<Member, String>
        writer.append("$member, $originalInput\n")
    }

    writer.close()
}

This works, but I was wondering if there was a "proper" Kotlin way of doing this?

Istvan Nagy
  • 310
  • 4
  • 13
yiwei
  • 4,022
  • 9
  • 36
  • 54

8 Answers8

124

A bit more idiomatic. For PrintWriter, this example:

File("somefile.txt").printWriter().use { out ->
    history.forEach {
        out.println("${it.key}, ${it.value}")
    }
}

The for loop, or forEach depends on your style. No reason to use append(x) since that is basically write(x.toString()) and you already give it a string. And println(x) basically does write(x) after converting a null to "null". And println() does the correct line ending.

If you are using data classes of Kotlin, they can already be output because they have a nice toString() method already.

Also, in this case if you wanted to use BufferedWriter it would produce the same results:

File("somefile.txt").bufferedWriter().use { out ->
    history.forEach {
        out.write("${it.key}, ${it.value}\n")
    }
}

Also you can use out.newLine() instead of \n if you want it to be correct for the current operating system in which it is running. And if you were doing that all the time, you would likely create an extension function:

fun BufferedWriter.writeLn(line: String) {
    this.write(line)
    this.newLine()
}

And then use that instead:

File("somefile.txt").bufferedWriter().use { out ->
    history.forEach {
        out.writeLn("${it.key}, ${it.value}")
    }
}

And that's how Kotlin rolls. Change things in API's to make them how you want them to be.

Wildly different flavours for this are in another answer: https://stackoverflow.com/a/35462184/3679676

Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
  • 1
    it may an other possible solution: File(fileName).writeText(Iterable.joinToString(), Charsets.UTF_8) – Istvan Nagy Feb 18 '16 at 09:52
  • @IstvanNagy depending on the size of the iterable and if you want to materialize the full string before writing. I'll add it to the alternative answer, it is nice and short! – Jayson Minard Feb 18 '16 at 12:05
  • For reference, since I spent about an hour trying to figure this out, if you're going to be opening and closing a file multiple times for appending text, use `FileWriter("output.txt", true).use {` for the 1st line. (Otherwise, the textfile is cleared each time you open it) – takanuva15 Jun 10 '18 at 15:32
  • Does that imply that the `printWriter` is not using any buffering? Which would make me wonder _why_? It feels wrong having to decide between either buffering or the convenience of having a line-writing function out-of-the-box. – bluenote10 Dec 16 '21 at 19:04
  • 1
    No @bluenote10 that is not implied, that was just another example of how to use a different interface to write. – Jayson Minard Dec 19 '21 at 00:39
36

Other fun variations so you can see the power of Kotlin:

A quick version by creating the string to write all at once:

File("somefile.txt").writeText(history.entries.joinToString("\n") { "${it.key}, ${it.value}" })
// or just use the toString() method without transform:
File("somefile.txt").writeText(x.entries.joinToString("\n"))

Or assuming you might do other functional things like filter lines or take only the first 100, etc. You could go this route:

File("somefile.txt").printWriter().use { out ->
    history.map { "${it.key}, ${it.value}" }
           .filter { ... }
           .take(100)
           .forEach { out.println(it) }
}

Or given an Iterable, allow writing it to a file using a transform to a string, by creating extension functions (similar to writeText() version above, but streams the content instead of materializing a big string first):

fun <T: Any> Iterable<T>.toFile(output: File, transform: (T)->String = {it.toString()}) {
    output.bufferedWriter().use { out ->
        this.map(transform).forEach { out.write(it); out.newLine() }
    }
}

fun <T: Any> Iterable<T>.toFile(outputFilename: String, transform: (T)->String = {it.toString()}) {
    this.toFile(File(outputFilename), transform)
}

used as any of these:

history.entries.toFile(File("somefile.txt")) {  "${it.key}, ${it.value}" }

history.entries.toFile("somefile.txt") {  "${it.key}, ${it.value}" }

or use default toString() on each item:

history.entries.toFile(File("somefile.txt")) 

history.entries.toFile("somefile.txt") 

Or given a File, allow filling it from an Iterable, by creating this extension function:

fun <T: Any> File.fillWith(things: Iterable<T>, transform: (T)->String = {it.toString()}) {
    this.bufferedWriter().use { out ->
        things.map(transform).forEach { out.write(it); out.newLine() }
    }
}

with usage of:

File("somefile.txt").fillWith(history.entries) { "${it.key}, ${it.value}" }

or use default toString() on each item:

File("somefile.txt").fillWith(history.entries) 

which if you had the other toFile extension already, you could rewrite having one extension call the other:

fun <T: Any> File.fillWith(things: Iterable<T>, transform: (T)->String = {it.toString()}) {
    things.toFile(this, transform)
}
Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
5

It mostly looks ok to me. The only thing different I would do is use the "use" extension defined in ReadWrite to auto close the writer.

PrintWriter("file.txt").use {
  for ((member, originalInput) in history) {  // history: Map<Member, String>
    it.append("$member, $originalInput\n")
  }    
}
Lionel Port
  • 3,492
  • 23
  • 26
3

At the very minimum, you could use:

FileWriter(filename).use { it.write(text) }

FileWriter is a convenience class for writing character files (provided by Java, and hence available in Kotlin). It extends Closeable, and hence can be used by Kotlin's ".use" extension method.

The .use extension method automatically closes the calling object once the block exits, thus providing an idiomatic way to close the file after it's written.

2
try{
      val fileWriter = FileWriter("test.txt", true)
      fileWriter.write(string+ "\n")
      fileWriter.close()
    } catch (exception: Exception){
        println(exception.message)
    }
Ajay Prajapati
  • 668
  • 1
  • 10
  • 18
1

Some Kotlin magic allows to omit referencing the stream on each read or write call:

fun <T : Closeable, R> T.useWith(block: T.() -> R): R = use { with(it, block) }

File("a.in").bufferedReader().useWith {
    File("a.out").printWriter().useWith {
        val (a, b) = readLine()!!.split(' ').map(String::toInt)
        println(a + b)
    }
}

Scanner(File("b.in")).useWith {
    PrintWriter("b.out").useWith {
        val a = nextInt()
        val b = nextInt()
        println(a + b)
    }
}
Vadzim
  • 24,954
  • 11
  • 143
  • 151
1

Example as easy

val path = context!!.filesDir.absolutePath // => /data/user/0/com.example.test/files
File("$path/filename.txt").writeText("hello")
Infomaster
  • 793
  • 7
  • 8
0

File(requireContext().filesDir, "TodayTaskListChange.txt").writeText("write your test here...")

sandip
  • 394
  • 1
  • 4
  • 11