You can use the writeBytes function:
fun File.writeBytes(array: ByteArray)
Answer from André Jesus on Stack Overflowjava - Convert large bytesarray to file in kotlin - Stack Overflow
android - Converting a byte array to a pdf file then saving it - Stack Overflow
About write a UByteArray to the file - Libraries - Kotlin Discussions
How do I write to a file in Kotlin? - Stack Overflow
I am making my first evolutionary sim while also learning Kotlin. I want to save the state of epochs (what happens in each simulation) and am making a binary encoding to help with efficiency. I want to write bytes to a file in a buffered fashion (write every 10kb or something).
I am trying to attack this by writing a ByteArray and tracking the number of bytes "added", and then writing and resetting when it hits the limit. This would require a conditional statement for every byte I write to the buffer though, I was wondering if this is naive?
Thanks in advance for any help!
This will get the Android download directory and write the byte array as a PDF file (assuming the byte array contains a PDF). Change File.createTempFile to any file you like (you don't need to create a temporary file):
fun writeBytesAsPdf(bytes : ByteArray) {
val path = requireContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
var file = File.createTempFile("my_file",".pdf", path)
var os = FileOutputStream(file);
os.write(bytes);
os.close();
}
You will also have to add android.permission.WRITE_EXTERNAL_STORAGE to your manifest.
Looking at How to download PDF file with Retrofit and Kotlin coroutines?, you can use:
private const val BUFFER_SIZE = 4 * 1024
private fun copyStreamToFile(inputStream: InputStream, outputFile: File) {
inputStream.use { input ->
val outputStream = FileOutputStream(outputFile)
outputStream.use { output ->
val buffer = ByteArray(BUFFER_SIZE)
while (true) {
val byteCount = input.read(buffer)
if (byteCount < 0) break
output.write(buffer, 0, byteCount)
}
output.flush()
}
}
}
or
private fun InputStream.saveToFile(file: String) = use { input ->
File(file).outputStream().use { output ->
input.copyTo(output)
}
}
Also you should create the file.
private fun createFile(context: Context, name: String): File? {
val storageDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.path
var file = File("$storageDir/$name.pdf")
return storageDir?.let { file }
}
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
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)
}