-
-
Save vudunguit/9b229a4f63d3dc45370dedbebee57d3c to your computer and use it in GitHub Desktop.
Download File with progress indicator, written in Kotlin with Co-routines
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
suspend fun downloadFile(url: String, | |
downloadFile: File, | |
downloadProgressFun: (bytesRead: Long, contentLength: Long, isDone: Boolean) -> Unit) { | |
async(CommonPool) { | |
val request = with(Request.Builder()) { | |
url(url) | |
}.build() | |
val client = with(OkHttpClient.Builder()) { | |
addNetworkInterceptor { chain -> | |
val originalResponse = chain.proceed(chain.request()) | |
val responseBody = originalResponse.body() | |
responseBody?.let { | |
originalResponse.newBuilder().body(ProgressResponseBody(it, | |
downloadProgressFun)).build() | |
} | |
} | |
}.build() | |
try { | |
val execute = client.newCall(request).execute() | |
val outputStream = FileOutputStream(downloadFile) | |
val body = execute.body() | |
body?.let { | |
with(outputStream) { | |
write(body.bytes()) | |
close() | |
} | |
} | |
} catch (e: Exception) { | |
e.printStackTrace() | |
} | |
} | |
} | |
//Coroutines and callbacks do not fit nice... So this function. | |
suspend fun downloadFile(url: String, downloadFile: File): Channel<ApiResponse<FileProgress>> { | |
val channel = Channel<ApiResponse<FileProgress>>() | |
async(CommonPool) { | |
val request = with(Request.Builder()) { | |
url(url) | |
}.build() | |
val client = with(OkHttpClient.Builder()) { | |
addNetworkInterceptor { chain -> | |
val originalResponse = chain.proceed(chain.request()) | |
if (originalResponse.isSuccessful) { | |
val responseBody = originalResponse.body() | |
responseBody?.let { | |
val progressResponseBody = ProgressResponseBody(it) { bytesRead, contentLength, isDone -> | |
launch(UI) { | |
channel.send(SuccessData(FileProgress(bytesRead = bytesRead, | |
contentLength = contentLength, | |
isDone = isDone))) | |
if (isDone) { | |
channel.close() | |
} | |
} | |
} | |
originalResponse.newBuilder().body(progressResponseBody).build() | |
} | |
} else { | |
launch(UI) { | |
channel.send(ApiError(errorCode = originalResponse.code(), | |
errorMessage = getCommonErrorMessage(originalResponse.code()))) | |
channel.close() | |
downloadFile.deleteRecursively() | |
} | |
originalResponse | |
} | |
} | |
}.build() | |
try { | |
val execute = client.newCall(request).execute() | |
val outputStream = FileOutputStream(downloadFile) | |
val body = execute.body() | |
body?.let { | |
with(outputStream) { | |
write(body.bytes()) | |
close() | |
} | |
} | |
} catch (e: Exception) { | |
launch(UI) { | |
channel.send(ApiError<FileProgress>(errorMessage = e.message ?: "Something gone wrong during download")) | |
channel.close() | |
downloadFile.deleteRecursively() | |
} | |
e.printStackTrace() | |
} | |
} | |
return channel | |
} | |
fun getCommonErrorMessage(code: Int): String = when (code) { | |
400 -> "This request is wrong" | |
401 -> "You are not authorised to access this file" | |
404 -> "The file/page is not found" | |
else -> "Something gone wrong" | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private fun downloadZip(zipUrl: String) { | |
launch(UI) { | |
try { | |
initProductDir() | |
val url = URL(zipUrl) | |
val fileName = url.path.substringAfterLast("/") | |
val kotlinExternalFileWriter = KotlinExternalFileWriter() | |
val createFile = if (!kotlinExternalFileWriter.isFileExists(fileName, productDir)) { | |
kotlinExternalFileWriter.createFile(fileName, parent = productDir) | |
} else { | |
File(productDir, fileName) | |
} | |
downloadFile(url = zipUrl, | |
downloadFile = createFile) { bytesRead, contentLength, isDone -> | |
launch(UI) { | |
try { | |
val percentage = (bytesRead * 100) / contentLength | |
txtProgress.text = "$percentage%" | |
progress.progress = percentage.toInt() | |
if (isDone) { | |
extractZipHere(createFile) | |
afterDataComes() | |
} | |
} catch (e: Exception) { | |
e.printStackTrace() | |
} | |
} | |
} | |
} catch (e: Exception) { | |
e.printStackTrace() | |
} | |
} | |
} | |
private fun downloadZipWithoutUrl(zipUrl: String) { | |
launch(UI) { | |
try { | |
initProductDir() | |
val fileName = "$productId.zip" | |
// val fileName = url.path.substringAfterLast("/") | |
val kotlinExternalFileWriter = KotlinExternalFileWriter() | |
val parentDirectory = productDir.parentFile | |
if (!kotlinExternalFileWriter.isFileExists(fileName, parentDirectory)) { | |
val createFile: File = kotlinExternalFileWriter.createFile(fileName, | |
parent = parentDirectory) | |
launch(UI) { | |
for (data in downloadFile(url = zipUrl, downloadFile = createFile)) { | |
when (data) { | |
is SuccessData<FileProgress> -> { | |
data.data?.let { | |
val (bytesRead, contentLength, isDone) = it | |
val percentage = (bytesRead * 100) / contentLength | |
if (isDone || percentage == 100L) { | |
progress.isIndeterminate = true | |
delay(2, SECONDS) | |
extractZipHere(createFile) { | |
afterExtract() | |
} | |
} | |
txtProgress.text = "$percentage%" | |
progress.progress = percentage.toInt() | |
} | |
} | |
is ApiError<FileProgress> -> { | |
progress.showSnackBar(data.errorMessage) | |
} | |
} | |
} | |
} | |
} else { | |
val createFile = File(parentDirectory, fileName) | |
progress.isIndeterminate = true | |
if (productDir.listFiles().isNotEmpty()) { | |
afterExtract() | |
} else { | |
extractZipHere(createFile) { | |
afterExtract() | |
} | |
} | |
} | |
} catch (e: Exception) { | |
e.printStackTrace() | |
progress.showSnackBar(e.message.toString()) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
data class FileProgress(val bytesRead: Long, val contentLength: Long, val isDone: Boolean) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import okhttp3.MediaType | |
import okhttp3.ResponseBody | |
import okio.* | |
class ProgressResponseBody(val responseBody: ResponseBody, | |
val downloadProgressFun: (bytesRead: Long, contentLength: Long, isDone: Boolean) -> Unit) : ResponseBody() { | |
lateinit var bufferedSource: BufferedSource | |
override fun contentLength(): Long = responseBody.contentLength() | |
override fun contentType(): MediaType? = responseBody.contentType() | |
override fun source(): BufferedSource { | |
if (!::bufferedSource.isInitialized) { | |
bufferedSource = Okio.buffer(source(responseBody.source())) | |
} | |
return bufferedSource | |
} | |
private fun source(source: Source): Source { | |
return object : ForwardingSource(source) { | |
var totalBytesRead: Long = 0 | |
override fun read(sink: Buffer, byteCount: Long): Long { | |
val read: Long = super.read(sink, byteCount) | |
totalBytesRead += if (read != -1L) read else 0 | |
downloadProgressFun(totalBytesRead, responseBody.contentLength(), read == -1L) | |
return read | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment