I wrote code that downloads some source of data from the internet (in this example picture), shows downloadPercentages while the process of downloading is ongoing and writes this file on android storage. works well and looks very nice everything except saving on android storage.
Code is written in 3 classes, but I will show only one that I think is relevant (DownloadWorker). If anyone thinks other classes might help, let me now in comment and I will add them.
DownloadWorker:
class DownloadWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
companion object {
const val FILE_NAME = "image.jpg"
}
override fun doWork(): Result {
try {
if (DownloadHelper.url == null) {
DownloadHelper.downloadState.postValue(DownloadState.Failure)
return Result.failure()
}
DownloadHelper.url?.apply {
if(!startsWith("https")) {
DownloadHelper.url = replace("http", "https")
}
}
val url = URL(DownloadHelper.url)
val connection = url.openConnection()
connection.connect()
val fileSize = connection.contentLength
val inputStream = connection.getInputStream()
val buffer = ByteArray(1024)
val file = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
val outputFile = File(file, FILE_NAME)
var len = 0
var total = 0L
val fos = FileOutputStream(outputFile)
len = inputStream.read(buffer)
while (len != -1) {
fos.write(buffer, 0, len)
total += len
val percent = ((total * 100) / fileSize).toInt()
DownloadHelper.downloadState.postValue(DownloadState.InProgress(percent))
len = inputStream.read(buffer)
}
fos.close()
inputStream.close()
DownloadHelper.downloadState.postValue(DownloadState.Success(outputFile))
} catch (e: Exception) {
DownloadHelper.downloadState.postValue(DownloadState.Failure)
return Result.failure()
}
return Result.success()
}
}
After download success, my image is not shown in gallery, or in downloaded files folder. To see this image you need to enter android storage, find in android data app package by name and navigate all the way to the image. Pretty complicated.
Can anyone help, thanks.
Related
I am currently writing an app where I need to create a zip file that has a bunch of bitmap images in it. I have a List that has the Uri's for all the images.
Could someone please direct me to how I can create a new zip file and then add all the images to a newly created zip file?
Assuming you have external storage permissions granted following should work
val BUFFER = 1024
fun Context.zip(files: Array<Uri>, zipFileName: String?) {
try {
var origin: BufferedInputStream? = null
val dest = FileOutputStream(zipFileName)
val out = ZipOutputStream(BufferedOutputStream(dest))
val data = ByteArray(BUFFER)
for (uri in files) {
val stringUri = uri.toString()
val fi = openFileInput(stringUri)
origin = BufferedInputStream(fi, BUFFER)
val entry = ZipEntry(stringUri.substring(stringUri.lastIndexOf("/") + 1))
out.putNextEntry(entry)
var count: Int
while (origin.read(data, 0, BUFFER).also { count = it } != -1) {
out.write(data, 0, count)
}
origin.close()
}
out.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
Remember this is an extension function on Context so it will require to be called with a context like context.zip(listOfUris, "ZIP_FILE_NAME_HERE")
Quite a few questions/answers and many I have tried without success. I receive a compressed string that uses a MemoryStream and DeflateStream to do so (c#). The following decompression function works fine
fun decompress(string: String): String? {
var decompressedString: String? = ""
try {
val bytes: ByteArray = Base64.decode(string, Base64.DEFAULT)
val inflater = Inflater(true)
val outputStream = ByteArrayOutputStream()
val buffer = ByteArray(1024)
inflater.setInput(bytes)
while (!inflater.finished()) {
val count = inflater.inflate(buffer)
outputStream.write(buffer, 0, count)
}
inflater.end()
outputStream.close()
decompressedString = outputStream.toString("UTF8")
} catch (e: IOException) {
e.printStackTrace()
}
return decompressedString
}
At a later time I need to compress the data and send it back. Attempts to compress the data have been unsuccessful. The server keeps telling me that the "block length does not match with its complement." I use the following function for compressing
fun compress(string: String): String? {
var compressedString: String? = null
try {
val bytes: ByteArray = string.toByteArray(charset("UTF-8"))
// Compress the bytes
val deflater = Deflater()
//val outputStream = ByteArrayOutputStream()
val buffer = ByteArray(1024)
deflater.setInput(bytes)
deflater.finish()
deflater.deflate(buffer)
deflater.end()
//outputStream.close()
compressedString = Base64.encodeToString(buffer, Base64.DEFAULT)
} catch (e: IOException) {
e.printStackTrace()
}
return compressedString
}
The problem isn't server side as it works fine with an iOS app but not Android. I've tried many variants of this all without success.
Anyone have any suggestions on what it is that I am doing incorrectly and what I need to do to get it to work?
Thanks ^.^
In case anyone else runs into this problem. I was able to solve it by changing the deflate function to
var compressedString: String? = ""
val bytes: ByteArray = string.toByteArray(charset("UTF-8"))
val deflater = Deflater(1, true)
deflater.setInput(bytes)
deflater.finish()
val outputStream = ByteArrayOutputStream(bytes.size)
try {
val bytesCompressed = ByteArray(Short.MAX_VALUE.toInt())
val numberOfBytesAfterCompression = deflater.deflate(bytesCompressed)
val returnValues = ByteArray(numberOfBytesAfterCompression)
System.arraycopy(bytesCompressed, 0, returnValues, 0, numberOfBytesAfterCompression)
compressedString = Base64.encodeToString(returnValues, Base64.DEFAULT)
} catch (e: IOException) {
e.printStackTrace()
} finally {
deflater.end()
outputStream.close()
}
Obtained from here deflater examples site.
Apparently using the prior function adds 2 additional bytes and this is what was causing the issue. After the change, the 2 bytes are not added. I don't quite understand how or why so if someone knows and wishes to share, please do so.
I'm using Storage Access Network to pick file and save in internal storage so that app can use if in future.
I'm getting URI without any issues. It's something like content://com.android.providers.media.documents/document/image%3A141274
Problem comes when I'm trying to save image into internal directory. Code passes without crashes, image with same size is saved into internal directory (I can see it in device Explorer: https://take.ms/3TwBS).
But image itself is broken and can't be opened.
Here's code I'm using (after getting URI)
val destinationFile = File("${context.filesDir.absolutePath}/$fileName")
try {
val writer = FileWriter(destinationFile)
writer.append(readTextFromUri(it))
writer.flush()
writer.close()
} catch (e: Exception) {
e.printStackTrace()
}
#Throws(IOException::class)
private fun readTextFromUri(uri: Uri): String {
val inputStream = activity!!.contentResolver.openInputStream(uri)
val reader = BufferedReader(InputStreamReader(inputStream))
val stringBuilder = StringBuilder()
var line: String? = null
while ({ line = reader.readLine(); line }() != null) {
stringBuilder.append(line)
}
inputStream?.close()
reader.close()
return stringBuilder.toString()
}
As #CommonsWare described I should have used proper dealing with files, not texts.
Proper way to do:
private fun inputStreamToFile(uri: Uri){
val inputStream = contentResolver.openInputStream(uri)
val output = FileOutputStream(File("${filesDir.absoluteFile}/magic.png"))
inputStream?.copyTo(output, 4 * 1024)
}
Or longer way (without extension functions)
fun inputStreamToFile(uri: Uri){
val inputStream = contentResolver.openInputStream(uri)
inputStream.use {
val directory = getDir("test", Context.MODE_PRIVATE)
val file = File(directory, "correct.txt")
val output = FileOutputStream(file)
output.use {
val buffer = ByteArray(4 * 1024) // or other buffer size
var read: Int = inputStream?.read(buffer) ?: -1
while (read != -1) {
output.write(buffer, 0, read)
read = inputStream?.read(buffer) ?: -1
}
output.flush()
}
}
}
I am downloading the file using OkHttp3, I want to see the downloading speed. but I am confused about how to measure the speed.
I tried getting the current millis before reading the buffer and calculating after it is written, but it always returns a static value.
Following is my download function.
fun download(fileName: String) {
val request = Request.Builder().url(url)
.get().build()
val call = OkHttpClient().newCall(request)
val response = call.execute()
if (response.isSuccessful) {
var inputStream: InputStream? = null
try {
inputStream = response.body()?.byteStream()
val buffer = ByteArray(8192)
val mediaFile = File(downloadDir, fileName)
val output = RandomAccessFile(mediaFile, "rw")
output.seek(0)
while (true) {
val readed = inputStream?.read(buffer)
if (readed == -1 || readed == null) {
break
}
output.write(buffer, 0, readed)
downloaded.append(readed.toLong())
}
output.close()
} catch (e: IOException) {
// TODO: handle IOException
console.log("${e.message}")
} finally {
inputStream?.close()
}
}
}
It's a very simple problem, I got confused by overthinking. Anyway here is the solution.
all I need to do is store the downloaded bytes in a variable after 1s subtract downloaded bytes from newly downloaded bytes, that will give me the downloaded bytes in 1s, then I can use those bytes to convert into speed like kbps or Mbps.
fun getSpeed(callback: (String) -> Unit) {
doAsync {
var prevDownloaded = 0L
while (true) {
if (contentLength != null) {
if (downloaded.get() >= contentLength!!) {
break
}
}
if (prevDownloaded != 0L) {
callback(formatBytes(downloaded.get() - prevDownloaded))
}
prevDownloaded = downloaded.get()
Thread.sleep(1000)
}
}
}
I have an array of URLs, each providing a zip file. I want to download them and store them in my app folders, inside the internal memory.
Question:
Since I do not know the number of URLs I will need to access, what is the best way to go about this? I am just beginning to work with Kotlin coroutines.
This is my 'download from url' method
fun downloadResourceArchiveFromUrl(urlString: String, context: Context): Boolean {
Timber.d("-> Started downloading resource archive.. $urlString")
lateinit var file: File
try {
val url = URL(urlString)
val urlConn = url.openConnection()
urlConn.readTimeout = 5000
urlConn.connectTimeout = 10000
val inputStream = urlConn.getInputStream()
val buffInStream = BufferedInputStream(inputStream, 1024 * 5)
val fileNameFromUrl = urlString.substringAfterLast("/")
file = File(context.getDir("resources", Context.MODE_PRIVATE) , fileNameFromUrl)
val outStream = FileOutputStream(file)
val buff = ByteArray(5 * 1024)
while (buffInStream.read(buff) != -1){
outStream.write(buff, 0, buffInStream.read(buff))
}
outStream.flush()
outStream.close()
buffInStream.close()
} catch (e: Exception) {
e.printStackTrace()
Timber.d("Download finished with exception: ${e.message} -<")
return false
}
Timber.d("Download finished -<")
return true
}
Could you simply create a loop and call download method each time?
for (i in resources.indices) {
asyncAwait {
downloadResourcesFromUrl(resources[i].url, context)
return#asyncAwait
}
Also, is it a good idea to do this synchronously? Wait for every file to download then proceed to the next one?
Turn your blocking download function into a suspending one:
suspend fun downloadResourceArchiveFromUrl(
urlString: String, context: Context
): Boolean = withContext(Dispatchers.IO) {
... your function body
}
Now run your loop inside a coroutine you launch:
myActivity.launch {
resources.forEach {
val success = downloadResourceArchiveFromUrl(it.url, context)
... react to success/failure ...
}
}
Also be sure to properly implement structured concurrency on your activity.