DEV Community

Cover image for SteadyFetch: A tiny Android SDK that refuses to let your downloads die
Naman Anand
Naman Anand

Posted on

SteadyFetch: A tiny Android SDK that refuses to let your downloads die

Most download bugs do not show up in crashlytics.

They show up as angry users, half written files, and silent failures when Android decides that your background work is no longer welcome. 😀

I hit this wall while working on the Microsoft Foundry Local Android App, where we ship large on device models that must stay confidential inside the app sandbox. πŸ”

That is where SteadyFetch was born.

Demo

SteadyFetch Example

Why Android DownloadManager was not enough πŸ€¦β€β™‚οΈ

Android DownloadManager is great when you want to save a PDF into Downloads and let the user open it from a notification. It is not so great when you care about security and control.

Here are the pain points that pushed me to build my own downloader:

  • You cannot directly stream into internal app storage with a simple DownloadManager request. The normal flow wants to write into public or shared locations. πŸ“‚
  • If you really need files inside context.filesDir or context.noBackupFilesDir, you usually end up doing a second copy step from a public URI into your sandbox. That is extra I/O and still leaves traces outside your app. 🧳
  • For sensitive assets, like ML models or licensed media, writing to public or semi public storage increases the risk that other apps or users can pull those files off the device. 🚨
  • The API surface is quite rigid. Chunked downloads, fine grained progress, and aggressive resume behavior are not first class concepts. Vendor quirks make the behavior even less predictable. πŸ˜…
  • DownloadManager can be surprisingly slow to start. You enqueue a request and Android decides when it actually begins, so you do not get strict "start now" behavior for time sensitive flows. 🐒
  • Even if your server supports HTTP range requests, DownloadManager does not do true parallel chunked downloads. You are stuck with a single stream and leave a lot of bandwidth and control on the table. πŸš€

For Foundry Local, letting model weights touch shared storage was not acceptable. We needed a pipeline that kept bytes inside our process and inside app only directories from the first byte to the last.

So I wrote the download logic myself. That code eventually evolved into a small, reusable SDK: SteadyFetch. ✨

What SteadyFetch gives you 🎁

SteadyFetch is a Kotlin first Android library that focuses on three things:

  • Privacy first storage: You choose the exact destination directory, including internal app storage. Files never need to sit in public downloads folders.
  • Parallel, resumable downloads: Large files are split into chunks and fetched in parallel with HTTP range requests. If the app dies or the network drops, SteadyFetch can resume from the chunks that are already on disk.
  • Foreground service without boilerplate: The library manages a foreground service and notification for you. Your code just queues downloads and reacts to callbacks.

The public API is intentionally small. In most apps you only need three calls.

// 1. Initialize once in your Application
SteadyFetch.initialize(this)

// 2. Queue a download
val id = SteadyFetch.queueDownload(
    request = DownloadRequest(
        url = "https://example.com/models/encoder.bin",
        fileName = "encoder.bin",
        downloadDir = File(context.filesDir, "models")
    ),
    callback = object : SteadyFetchCallback {
        override fun onUpdate(progress: DownloadProgress) {
            // Update a ProgressBar or show a notification
        }

        override fun onSuccess() {
            // Model is ready inside internal storage
        }

        override fun onError(error: DownloadError) {
            // Log or show a friendly message
        }
    }
)

// 3. Cancel if you really need to
SteadyFetch.cancelDownload(id)
Enter fullscreen mode Exit fullscreen mode

How it works under the hood in one minute 🧠

Very simplified, the internal flow looks like this:

  1. Probe the server to figure out file size and whether range requests are supported.
  2. Split the file into chunks, each with a byte range and its own temporary file name.
  3. Download chunks in parallel using OkHttp and coroutines, throttled by a configurable concurrency limit.
  4. Write directly into temp files under your chosen directory, usually internal storage.
  5. On restart, scan existing chunk files, skip finished ones, and resume partial ones instead of starting over.
  6. When all chunks are done, merge them atomically into the final target file and clean up the temp parts.

The filesystem itself becomes the source of truth for progress. There is no extra database layer and no dependency on public storage.

Who should care ❀️

If DownloadManager feels too blunt for your use case and you want something small, fast, and friendly to internal storage, SteadyFetch might be just what you need ✌️

Repo link:

https://github.com/void-memories/SteadyFetch

Top comments (0)