How to stream a response body to a file in KMP on both Android and iOS using the Ktor client?
The following expected function, which writes data to a file read from the ByteReadChannel
, can be added to the common code:
expect suspend fun ByteReadChannel.writeToFile(filepath: String)
Here’s an example of the above function usage:
val client = HttpClient()
client.prepareGet("https://httpbin.org/image/jpeg").execute {
it.bodyAsChannel().writeToFile("/path/to/file")
}
For JVM and Android, the File.writeChannel
can be used to stream the ByteReadChannel
to the file:
actual suspend fun ByteReadChannel.writeToFile(filepath: String) {
this.copyTo(File(filepath).writeChannel())
}
For iOS, the dispatch_write function can be used to asynchronously write to the file while reading chunks from the ByteReadChannel
:
private const val BUFFER_SIZE = 4096
@OptIn(ExperimentalForeignApi::class)
actual suspend fun ByteReadChannel.writeToFile(filepath: String) {
val channel = this
val queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.convert(), 0u)
memScoped {
val dst = allocArray<ByteVar>(BUFFER_SIZE)
val fd = open(filepath, O_RDWR)
try {
while (!channel.isClosedForRead) {
val rs = channel.readAvailable(dst, 0, BUFFER_SIZE)
if (rs < 0) break
val data = dispatch_data_create(dst, rs.convert(), queue) {}
dispatch_write(fd, data, queue) { _, error ->
if (error != 0) {
channel.cancel(IllegalStateException("Unable to write data to the file $filepath"))
}
}
}
} finally {
close(fd)
}
}
}