
Streaming downloads with observable tasks, pause/resume, retry, priorities and bounded concurrency; supports segmented ranges, checksums, safe file finalization, plus injectable HTTP client and scope.
Yukitoki is an in-process Kotlin Multiplatform download manager for Android, JVM desktop, and iOS. It provides streaming downloads, observable tasks, pause/resume, retry, priorities, bounded concurrency, segmented ranges, checksums, and safe file finalization.
The browser target exposes download intents because browsers do not expose an ordinary writable Downloads filesystem.
The latest release is:
implementation("io.github.darkryh.yukitoki:download-manager:1.0.0-beta08")The artifact bundles an appropriate Ktor engine for Android, JVM, and iOS.
val manager = DownloadManager.create()
val task = manager.enqueue("https://example.com/report.pdf")
coroutineScope {
// task.state never completes, so collect it beside the await and cancel it afterwards.
val progress = launch { task.state.collect { snapshot -> println(snapshot?.state) } }
when (val result = task.await()) {
is DownloadResult.Success -> println("Saved to ${result.file.path}")
is DownloadResult.Failure -> println("Failed: ${result.failure}")
DownloadResult.Cancelled -> println("Cancelled")
}
progress.cancel()
}
manager.close()No platform expect/actual, Ktor engine, coroutine scope, Android initialization, or explicit path is required for the default case.
val defaultDirectory = DownloadLocations.downloads
manager.enqueue(
DownloadRequest(
url = "https://example.com/model.bin",
destination = DownloadDestination.Directory(
path = DownloadLocations.downloads / "models",
),
)
)DownloadLocations.downloads is writable and platform appropriate, but can be app-sandboxed. On Android it is app-specific storage; writing to the shared public Downloads collection requires an Android MediaStore/URI workflow.
Inject a caller-owned client and scope when engine-specific configuration is required:
val manager = DownloadManager.create(
httpClient = myHttpClient,
scope = applicationScope,
)Injected resources remain caller-owned. A manager created without injected resources owns and closes its client and scope.
Downloads never inherit a total request timeout: every download request opts out of one, including the CIO engine's built-in 15-second default, so a transfer can run as long as bytes keep arriving. Your client's connect and socket timeouts still apply, and DownloadManagerConfig.idleTimeout fails a download whose server stops responding.
HttpClient) engine and Linux XDG Downloads-directory support.Documents/Downloads.DownloadIntents; managed filesystem downloads are unsupported.Yukitoki does not persist its queue across process death or reboot, and does not run in the background on its own. Project status explains this decision and what to do instead.
The API is Kotlin-first. The iOS artifact is intended for shared Kotlin/Compose code; a separate Swift-native façade is not part of the 1.0 contract.
The project uses Kotlin explicit API mode and built-in ABI validation. Run:
./gradlew :download-manager:allTests :download-manager:build :download-manager:checkKotlinAbiintegration-tests runs real downloads over real sockets and in real browsers:
integration-tests/run.shApache License 2.0 — see LICENSE.
Yukitoki is an in-process Kotlin Multiplatform download manager for Android, JVM desktop, and iOS. It provides streaming downloads, observable tasks, pause/resume, retry, priorities, bounded concurrency, segmented ranges, checksums, and safe file finalization.
The browser target exposes download intents because browsers do not expose an ordinary writable Downloads filesystem.
The latest release is:
implementation("io.github.darkryh.yukitoki:download-manager:1.0.0-beta08")The artifact bundles an appropriate Ktor engine for Android, JVM, and iOS.
val manager = DownloadManager.create()
val task = manager.enqueue("https://example.com/report.pdf")
coroutineScope {
// task.state never completes, so collect it beside the await and cancel it afterwards.
val progress = launch { task.state.collect { snapshot -> println(snapshot?.state) } }
when (val result = task.await()) {
is DownloadResult.Success -> println("Saved to ${result.file.path}")
is DownloadResult.Failure -> println("Failed: ${result.failure}")
DownloadResult.Cancelled -> println("Cancelled")
}
progress.cancel()
}
manager.close()No platform expect/actual, Ktor engine, coroutine scope, Android initialization, or explicit path is required for the default case.
val defaultDirectory = DownloadLocations.downloads
manager.enqueue(
DownloadRequest(
url = "https://example.com/model.bin",
destination = DownloadDestination.Directory(
path = DownloadLocations.downloads / "models",
),
)
)DownloadLocations.downloads is writable and platform appropriate, but can be app-sandboxed. On Android it is app-specific storage; writing to the shared public Downloads collection requires an Android MediaStore/URI workflow.
Inject a caller-owned client and scope when engine-specific configuration is required:
val manager = DownloadManager.create(
httpClient = myHttpClient,
scope = applicationScope,
)Injected resources remain caller-owned. A manager created without injected resources owns and closes its client and scope.
Downloads never inherit a total request timeout: every download request opts out of one, including the CIO engine's built-in 15-second default, so a transfer can run as long as bytes keep arriving. Your client's connect and socket timeouts still apply, and DownloadManagerConfig.idleTimeout fails a download whose server stops responding.
HttpClient) engine and Linux XDG Downloads-directory support.Documents/Downloads.DownloadIntents; managed filesystem downloads are unsupported.Yukitoki does not persist its queue across process death or reboot, and does not run in the background on its own. Project status explains this decision and what to do instead.
The API is Kotlin-first. The iOS artifact is intended for shared Kotlin/Compose code; a separate Swift-native façade is not part of the 1.0 contract.
The project uses Kotlin explicit API mode and built-in ABI validation. Run:
./gradlew :download-manager:allTests :download-manager:build :download-manager:checkKotlinAbiintegration-tests runs real downloads over real sockets and in real browsers:
integration-tests/run.shApache License 2.0 — see LICENSE.