Reliable parallel + resumable downloads for Android, written in Kotlin.
SteadyFetch is a Kotlin SDK for Android that provides reliable, resumable downloads. It handles chunking, storage checks, notifications, and foreground service requirements so your app can focus on product logic instead of download plumbing.
- Feature Highlights
- Quick Facts
- Installation
- Quickstart (5 minutes)
- Usage Example
- API Reference
- How It Works (High Level)
- FAQ
- Roadmap
- Contributing
- License
- Parallel chunk downloads – Splits files into chunks and fetches them concurrently for faster throughput.
- Foreground-friendly execution – Keeps long transfers alive via a dedicated foreground service + notification flow.
- Resume support – Persists progress and resumes exactly where a transfer stopped (app kill, process death, or network drop).
- Checksum + storage validation – Validates remote metadata and ensures sufficient storage before writing.
- Well-tested core – Unit tests cover the controller, networking layer, chunk math, and error propagation.
Download surviving app kills + network changes with chunk-level progress:
Steady.Fetch.Example.mp4
- Use case – Resumable downloads with chunk-level progress and safe foreground execution.
- Tech stack – Kotlin, OkHttp, Android Service + Notification APIs.
- Minimum OS – Android 9 (API 28); builds against SDK 36.
- Distribution – Published via JitPack under
dev.namn.steady-fetch:steady-fetch. - Status – Early-stage, actively evolving. APIs may change before
1.0.0.
Add JitPack once in your root settings.gradle.kts:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven(url = "https://jitpack.io")
}
}Pull in the dependency in your app module:
dependencies {
implementation("dev.namn.steady-fetch:steady-fetch:<version>")
}🔎 Latest versions are listed on JitPack:
https://jitpack.io/#void-memories/SteadyFetch
class SteadyFetchApp : Application() {
override fun onCreate() {
super.onCreate()
SteadyFetch.initialize(this)
}
}val downloadId = SteadyFetch.queueDownload(
request = DownloadRequest(
url = "https://files.example.com/iso/latest.iso",
fileName = "latest.iso",
downloadDir = File(context.filesDir, "downloads")
),
callback = object : SteadyFetchCallback {
override fun onSuccess() {
// Update UI, notify user, etc.
}
override fun onUpdate(progress: DownloadProgress) {
// E.g. show progress in a notification or Compose UI
}
override fun onError(error: DownloadError) {
// Log & show an error state
}
}
)SteadyFetch.cancelDownload(downloadId)That’s enough to get a resilient, resumable download up and running.
SteadyFetch is designed to be called from your UI or domain layer, while the heavy lifting runs in a dedicated service.
A typical flow:
- User taps a “Download” button.
- You call
SteadyFetch.queueDownload()with aDownloadRequest. - You observe
SteadyFetchCallback.onUpdate()and update your UI. - On success or error, you update local state / DB accordingly.
Example with a simple wrapper:
fun startIsoDownload(context: Context) {
val downloadsDir = File(context.filesDir, "downloads")
val request = DownloadRequest(
url = "https://files.example.com/iso/latest.iso",
fileName = "latest.iso",
downloadDir = downloadsDir
)
val callback = object : SteadyFetchCallback {
override fun onSuccess() {
// Maybe emit an event to your ViewModel or show a snackbar
}
override fun onUpdate(progress: DownloadProgress) {
// progress.percent, progress.bytesDownloaded, etc.
}
override fun onError(error: DownloadError) {
// Map error to something user-friendly
}
}
SteadyFetch.queueDownload(request, callback)
}All public entry points live under the dev.namn.steady_fetch package and are backed by unit tests (MockK, Robolectric, MockWebServer).
Initializes the SteadyFetch SDK. Must be called once before using any other APIs, typically in your Application.onCreate().
Parameters:
application: Application- Your application instance
Throws: IllegalStateException if initialization fails
Enqueues a new download and returns a unique download ID. The download runs asynchronously in a foreground service.
Parameters:
request: DownloadRequest- Download configuration (see Data Models)callback: SteadyFetchCallback- Callback interface for progress updates and completion
Returns: Long - Unique download ID that can be used to cancel the download
Throws: IllegalStateException if SteadyFetch is not initialized
Cancels an active download by its ID.
Parameters:
downloadId: Long- The download ID returned fromqueueDownload()
Returns: Boolean - true if a download was found and cancelled, false otherwise
Configuration object for a download request.
data class DownloadRequest(
val url: String, // Required: URL of the file to download
val headers: Map<String, String> = emptyMap(), // Optional: Custom HTTP headers (e.g., auth tokens)
val maxParallelDownloads: Int = 4, // Optional: Max concurrent chunks (1-25, default 4)
val downloadDir: File, // Required: Directory where the file will be saved
val fileName: String, // Required: Name for the final downloaded file
)Fields:
url- The HTTP/HTTPS URL of the file to downloadheaders- Optional map of HTTP headers (useful for authentication, custom user agents, etc.)maxParallelDownloads- Maximum number of chunks to download concurrently. Must be between 1 and 25. Higher values may improve speed on fast connections but can overwhelm slower networks.downloadDir- The directory where the file will be saved. Must be writable. The directory will be created if it doesn't exist.fileName- The name of the final file after download completes (e.g.,"video.mp4","document.pdf")
Interface for receiving download progress updates and completion notifications.
interface SteadyFetchCallback {
fun onSuccess()
fun onUpdate(progress: DownloadProgress)
fun onError(error: DownloadError)
}Methods:
onSuccess()- Called when the download completes successfully. The file is available atdownloadDir/fileName.onUpdate(progress: DownloadProgress)- Called periodically with progress updates. SeeDownloadProgressbelow for details.onError(error: DownloadError)- Called when the download fails. SeeDownloadErrorbelow for details.
Snapshot of the current download state, including overall progress and per-chunk status.
data class DownloadProgress(
val status: DownloadStatus, // Current overall download status
val progress: Float, // Overall progress (0.0 to 1.0)
val chunkProgress: List<DownloadChunkProgress> // Per-chunk progress details
)Fields:
status- Current status of the download (seeDownloadStatusenum below)progress- Overall progress as a float between 0.0 (0%) and 1.0 (100%)chunkProgress- List of progress information for each chunk (empty for non-chunked downloads)
Progress information for a single chunk.
data class DownloadChunkProgress(
val status: DownloadStatus, // Status of this chunk
val name: String, // Chunk filename (e.g., "file.iso.part01")
val progress: Float // Chunk progress (0.0 to 1.0)
)Fields:
status- Current status of this chunkname- The temporary filename of this chunk (useful for debugging)progress- Progress of this chunk as a float between 0.0 and 1.0
Error information when a download fails.
data class DownloadError(
val code: Int, // Error code (HTTP status code if available, or -1)
val message: String // Human-readable error message
)Fields:
code- Error code. If the error is HTTP-related, this will be the HTTP status code (e.g., 404, 500). Otherwise, it may be -1 for generic errors, 499 for cancellations, 400 for bad requests, or 500 for internal errors.message- Descriptive error message explaining what went wrong
Enum representing the current state of a download or chunk.
enum class DownloadStatus {
QUEUED, // Download is queued but not yet started
RUNNING, // Download is actively in progress
FAILED, // Download has failed
SUCCESS // Download completed successfully
}Values:
QUEUED- The download is queued and waiting to startRUNNING- The download is actively transferring dataFAILED- The download encountered an error and stoppedSUCCESS- The download completed successfully
At a high level, SteadyFetch:
- Inspects the remote file – Uses a HEAD/metadata request to determine file size and validate the response.
- Splits into chunks – Computes chunk ranges and builds
Range-based HTTP requests. - Downloads in parallel – Fetches chunks concurrently using OkHttp and writes them to temporary files.
- Validates & merges – Optionally checks integrity, then merges chunks into the final file.
- Survives process death – Persists download state and resumes where it left off when the app or process restarts.
- Runs in a foreground service – Uses a dedicated service + ongoing notification to comply with Android’s background execution limits.
You don’t have to manage threads, chunk math, or foreground-service boilerplate yourself.
Planned / potential features:
- Parallel chunk downloads
- Resume support
- Foreground-service based execution
- Progress notification helpers
- Configurable retry/backoff policy
If you have a use case that isn’t covered yet, please open an issue.
Contributions, bug reports, and feature requests are very welcome.
- Bugs – Open an issue with steps to reproduce and logs if possible.
- Features – Open an issue first so we can discuss the design.
- PRs – Try to include unit tests where it makes sense and keep changes focused.
If you’re unsure whether something belongs in core or should be an extension, feel free to open a “question” issue.
SteadyFetch is distributed under the MIT License.
If you use this library in production, consider linking back to this repository so others can discover it.