Settings
Display
← blog

3 months of OOM crashes until I actually read the docs

Ever since I migrated 3DCollection to KMP I’ve been getting a steady stream of OOM crashes in Crashlytics. I never paid much attention because they were always on low-memory devices — but they bothered me.

Until one update I decided to actually fix it. At first I thought the file wasn’t being saved to disk correctly, so I added a write channel. Crashes kept coming. Then I started suspecting Ktor’s HttpClient and, reading the documentation with more patience than I should have had from the start, I found it.

The problem

File downloads were using Ktor’s client.get():

val response = service.client.get(urlDownload)

What I didn’t know is that .get() loads the entire response body into memory. Download a 20MB PDF → 20MB in RAM. On a device with 1GB of RAM shared between the OS and all running apps, that’s enough to blow it up.

The fix

The solution is prepareGet().execute { }, which opens a streaming channel and writes directly to the destination without going through memory:

service.client.prepareGet(urlDownload).execute { response ->
    val channel = response.bodyAsChannel()
    // write channel directly to sink/file
}

It’s a simple change, but you wouldn’t know without reading the docs — .get() looks like the obvious choice. With .get() you get all the bytes in memory. With prepareGet().execute { } you get a channel that flows directly to disk.

What I take away

Two things. The first is to try to understand better and question whether what you always do might sometimes be wrong — or simply not designed for that use case. The second is that crashes that look stupid break your brain, but at the same time, having zero OOM crashes is a great feeling.