While trying to save an image I am getting the error message 'Unresolved reference:runOnUiThread' and also on the context terms inside the toast messages. The relevant code snippets are as below. I have tried all possible fixes, but failed. Please help!
private suspend fun saveDrawnView(myBitmap: Bitmap): String {
var result = ""
withContext(Dispatchers.IO) {
if (myBitmap != null) {
try {
val bytes = ByteArrayOutputStream()
myBitmap.compress(Bitmap.CompressFormat.PNG, 90, bytes)
// val externalCacheDir = null
val f = File(
getExternalStorageDirectory()?.absolutePath.toString() +
File.separator + "com.example.drawingboardforkids" + System.currentTimeMillis() / 1000 + ".PNG"
)
val fo = FileOutputStream(f)
fo.write(bytes.toByteArray())
fo.close()
result = f.absolutePath
runOnUiThread{
if (!result.isEmpty()) {
Toast.makeText(
this#MainActivity,
"File saved successfully :$result",
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
this#MainActivity,
"Something went wrong while saving the file.",
Toast.LENGTH_SHORT
).show()
}
}
} catch (e: Exception) {
result = ""
e.printStackTrace()
}
}
}
return result
}
}
I've checked the coroutine dependencies inside build gradle, went throught clean, rebuild, invalidate caches, restart the pc etc., but the problem persists and hence the query. Please help!
The problem resolved. It occurred because the function was out of the scope of the MainActivity. Such a silly mistake..sorry for taking up the space and time.
Related
In an Android app the user can record his voice. The sound recording is saved in a file defined by:
audioTempFile = File(getFilesDir(), "Audio_Temp_File")
Then I want to give the user the choice of the final folder and file name for the file to be saved.
So the audioTempFile above can be used for a possible next recording without destroying the current one.
For example let us assume I want the file to be saved in this file called finalFile :
val rootDir = getFilesDir()
val storeDir = File(rootDir, "MyStorageDirectory")
val finalFile = File(storeDir, "MyFinalFileName")
How can I move or possibly copy audioTempFile to finalFile?
I did not find any clear answer by searching the net.
I don't know about saving to a specific location, but this is the way I'm saving the recordings in my app.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
try{
val values = ContentValues()
values.put(MediaStore.MediaColumns.DISPLAY_NAME, allFiles[position].name)
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3")
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_MUSIC)
val savedAudio = context.contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values)
val outputStream = savedAudio?.let {
context.contentResolver.openOutputStream(it)
}
val fis = FileInputStream(allFiles[position])
var length : Int
val buffer = ByteArray(8192)
while (fis.read(buffer).also { length = it } > 0) {
outputStream?.write(buffer, 0,length)
}
Toast.makeText(context, "Audio Saved to Music Folder", Toast.LENGTH_SHORT).show()
}catch (e : IOException){
Toast.makeText(
context,
"There was an error saving the file",
Toast.LENGTH_SHORT
).show()
e.printStackTrace()
}
}else{
try {
val audioDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)
val audio = File(audioDir, allFiles[position].name)
val fis = FileInputStream(allFiles[position])
val fos = FileOutputStream(audio)
var length : Int
val buffer = ByteArray(8192)
while (fis.read(buffer).also { length = it } > 0){
fos.write(buffer,0,length)
}
Toast.makeText(context, "Audio Saved to Music Folder", Toast.LENGTH_SHORT).show()
}catch (e : IOException){
Toast.makeText(
context,
"There was an error saving the file",
Toast.LENGTH_SHORT
).show()
e.printStackTrace()
}
}
You would need MANAGE_EXTERNAL_STORAGE permission if u want to save files other than public directories. It's recommended to use MediaStore API to save your files.
Do correct me if I'm wrong as I'm still learning.
Hope this helps u :)
I have a page in my application where the user select an image from the gallery and save it as part of needed details. The image will be sent to a server through API request using Ktor. I have tried to convert the image bitmap to file but I get "operation not permitted" exception, and tired to look for a solution but nothing worked. One of the solutions recommended to add use permission for storage in the Manifest:
It didn't work as well.
The code is too long but here the block where I need to convert and save the file:
fun bitmapToFile(bitmap: Bitmap, fileNameToSave: String): File? { // File name like "image.png"
//create a file to write bitmap data
var file: File? = null
return try {
file = File(Environment.DIRECTORY_PICTURES + File.separator + fileNameToSave)
file.createNewFile()
//Convert bitmap to byte array
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos) // YOU can also save it in JPEG
val bitmapData = bos.toByteArray()
//write the bytes in file
val fos = FileOutputStream(file)
fos.write(bitmapData)
fos.flush()
fos.close()
file
} catch (e: Exception) {
Toast.makeText( context, e.message, Toast.LENGTH_LONG ).show()
e.printStackTrace()
file // it will return null
}
}
fun handleChangeImage() {
try{
imageUri?.let {
if (Build.VERSION.SDK_INT < 28) {
bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, it)
} else {
val source = ImageDecoder.createSource(context.contentResolver, it)
bitmap = ImageDecoder.decodeBitmap(source)
}
}
//fixme: file conversion throw operation denied error
val fileName = imageUri?.lastPathSegment.toString().replace(":", ".")
val fileInput = if (bitmap != null)
bitmapToFile(bitmap!!, fileName)
else null
if(fileInput == null)
throw Exception("Error")
assetImageInput.value = fileInput
} catch (e: FileNotFoundException) {
Toast.makeText( context, e.message, Toast.LENGTH_LONG ).show()
} catch (e: Exception) {
Toast.makeText( context, e.message, Toast.LENGTH_LONG ).show()
}
}
After converting the image it should be saved as "assetImageInput.value" and be sent through the API as a parameter.
I created a suspended function that I am calling from ViewModel, the function takes a set of MyFile abstract class and then iterates through the set to get values and eventually insert them via content resolver.
There is no way the set values are being changed. It is immutable set after all. But still somehow, as the execution reaches insert function it throws ConcurrentModificationException
NOTE: The first iteration went smooth. It's the second one that causes the crash.
Please can anyone help me with this?
Thank you
suspend fun unhideSelectedFiles(files: Set<MyFile>, address: String? = null): Flow<UIEvent> = flow {
val context = App.getContext()
files.forEach { currentFile ->
Log.i(TAG, "unhideSelectedFiles: currentFile: ${currentFile.name}")
if (currentFile.currentPath.isBlank()) {
Log.e(TAG, "unhideSelectedFiles: ${currentFile.name} does not contain a current path")
return#forEach
}
val collection =
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.Q){
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
}else{
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val contentValues: ContentValues =
ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, currentFile.name)
put(MediaStore.Images.Media.MIME_TYPE, currentFile.mimeType)
put(MediaStore.Images.Media.IS_PENDING, 1)
put(
MediaStore.Images.Media.RELATIVE_PATH,
address ?: currentFile.originalPathWithoutFileName
)
}
val uri = context.contentResolver.insert(collection, contentValues)
uri?.let {
try {
context.contentResolver.openOutputStream(it)?.use { outputStream ->
val buf = ByteArray(COPY_BYTE_SIZE)
val inputStream = FileInputStream(File(currentFile.currentPath))
var len: Int
while (inputStream.read(buf).also { len = it } > 0) {
outputStream.write(buf, 0, len)
}
outputStream.close()
inputStream.close()
contentValues.clear()
contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0)
val isUpdated =
context.contentResolver.update(it, contentValues, null, null)
if (isUpdated > 0) {
if (File(currentFile.currentPath).delete()) {
emit(UIEvent.FileDone(currentFile))
Log.i(
TAG,
"unhideSelectedFiles: ${currentFile.name} unhidden successfully"
)
} else {
Log.e(
TAG,
"unhideSelectedFiles: Could not delete the image file from internal storage",
)
emit(UIEvent.Error("Could not delete ${currentFile.name} from hidden directory"))
}
} else {
Log.e(
TAG,
"unhideSelectedFiles: something went wrong with the file: ${currentFile.name}",
)
emit(UIEvent.Error("Something went wrong with the file: ${currentFile.name}"))
}
}
} catch (e: Exception) {
Log.e(
TAG,
"unhideSelectedFiles: file: ${currentFile.name}\n\nException: ${e.localizedMessage}",
)
emit(UIEvent.Error("Something went wrong: ${e.localizedMessage}"))
}
}
Log.i(TAG, "unhideSelectedFiles: file ${currentFile.name} task completed")
}
emit(UIEvent.Finished())
}
Here is the stack trace:
2022-03-31 06:47:25.806 13886-6547/com.androidbull.incognito.vaultreborn E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-2
Process: com.androidbull.incognito.vaultreborn, PID: 13886
java.util.ConcurrentModificationException
at java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:757)
at java.util.LinkedHashMap$LinkedKeyIterator.next(LinkedHashMap.java:780)
at com.androidbull.incognito.vaultreborn.utils.UtilsKt$unhideSelectedFiles$1.invokeSuspend(Utils.kt:302)
at com.androidbull.incognito.vaultreborn.utils.UtilsKt$unhideSelectedFiles$1.invoke(Unknown Source:8)
at com.androidbull.incognito.vaultreborn.utils.UtilsKt$unhideSelectedFiles$1.invoke(Unknown Source:4)
at kotlinx.coroutines.flow.SafeFlow.collectSafely(Builders.kt:61)
at kotlinx.coroutines.flow.AbstractFlow.collect(Flow.kt:212)
at com.androidbull.incognito.vaultreborn.viewModels.PhotosViewModel$unhideFiles$1.invokeSuspend(PhotosViewModel.kt:387)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
The function unhideSelectedFiles(..) is being called from a ViewModel just like this:
fun unhideFiles(selectedImages: Set<MyFile>, address: String? = null) {
viewModelScope.launch(IO) {
unhideSelectedFiles(selectedImages, address)
.collect {
if (it is UIEvent.FileDone) {
repository.deleteImage((it.file as ImageFile).toImageEntity())
} else {
/**
* you can show the progress of file by emitting the received flow and catching
* it in Fragment and showing dialog/bottomsheet accordingly
*/
}
}
}
}
Basically, I am trying to download three different images(bitmaps) from a URL and save them to Apps Internal storage, and then use the URI's from the saved file to save a new Entity to my database. I am having a lot of issues with running this in parallel and getting it to work properly. As ideally all three images would be downloaded, saved and URI's returned simultaneously. Most of my issues come from blocking calls that I cannot seem to avoid.
Here's all of the relevant code
private val okHttpClient: OkHttpClient = OkHttpClient()
suspend fun saveImageToDB(networkImageModel: CBImageNetworkModel): Result<Long> {
return withContext(Dispatchers.IO) {
try {
//Upload all three images to local storage
val edgesUri = this.async {
val req = Request.Builder().url(networkImageModel.edgesImageUrl).build()
val response = okHttpClient.newCall(req).execute() // BLOCKING
val btEdges = BitmapFactory.decodeStream(response.body?.byteStream())
return#async saveBitmapToAppStorage(btEdges, ImageType.EDGES)
}
val finalUri = this.async {
val urlFinal = URL(networkImageModel.finalImageUrl) // BLOCKING
val btFinal = BitmapFactory.decodeStream(urlFinal.openStream())
return#async saveBitmapToAppStorage(btFinal, ImageType.FINAL)
}
val labelUri = this.async {
val urlLabels = URL(networkImageModel.labelsImageUrl)
val btLabel = BitmapFactory.decodeStream(urlLabels.openStream())
return#async saveBitmapToAppStorage(btLabel, ImageType.LABELS)
}
awaitAll(edgesUri, finalUri, labelUri)
if(edgesUri.getCompleted() == null || finalUri.getCompleted() == null || labelUri.getCompleted() == null) {
return#withContext Result.failure(Exception("An image couldn't be saved"))
}
} catch (e: Exception) {
Result.failure<Long>(e)
}
try {
// Result.success( db.imageDao().insertImage(image))
Result.success(123) // A placeholder untill I actually get the URI's to create my Db Entity
} catch (e: Exception) {
Timber.e(e)
Result.failure(e)
}
}
}
//Save the bitmap and return Uri or null if failed
private fun saveBitmapToAppStorage(bitmap: Bitmap, imageType: ImageType): Uri? {
val type = when (imageType) {
ImageType.EDGES -> "edges"
ImageType.LABELS -> "labels"
ImageType.FINAL -> "final"
}
val filename = "img_" + System.currentTimeMillis().toString() + "_" + type
val file = File(context.filesDir, filename)
try {
val fos = file.outputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.close();
} catch (e: Exception) {
Timber.e(e)
return null
}
return file.toUri()
}
Here I am calling this function
viewModelScope.launch {
val imageID = appRepository.saveImageToDB(imageNetworkModel)
withContext(Dispatchers.Main) {
val uri = Uri.parse("$PAINT_DEEPLINK/$imageID")
navManager.navigate(uri)
}
}
Another issue I am facing is returning the URI in the first place and handling errors. As if one of these parts fails, I'd like to cancel the whole thing and return Result.failure(), but I am unsure on how to achieve that. As returning null just seems meh, I'd much prefer to have an error message or something along those lines.
I've been trying to use khttp to send an .jpg file in an android activity but haven't been able to make it work.
fun sendImage(view: View) {
try {
var bmp = (imageView?.drawable as BitmapDrawable).bitmap
var bos = ByteArrayOutputStream()
bmp.compress(Bitmap.CompressFormat.JPEG, 0, bos)
var response: Response? = null
findViewById<TextView>(R.id.image_desc).text = "Connecting to " + SERVER_URL;
try {
val job=GlobalScope.launch {
response = post(SERVER_URL, files = listOf(File(path).fileLike(name = "Image.jpg")))
}
findViewById<TextView>(R.id.image_desc).text = "Image contains: ${response?.text}"
} catch (e: Exception) {
findViewById<TextView>(R.id.image_desc).text = "Connection failed - please check fields are valid"
findViewById<TextView>(R.id.image_desc).text = e.toString()
}
} catch (e: UnknownHostException) {
findViewById<TextView>(R.id.image_desc).text = "Unknown host :("
e.printStackTrace()
} catch (e: IOException) {
findViewById<TextView>(R.id.image_desc).text = "IO exceptiion :("
e.printStackTrace()
} catch (e: Exception) {
findViewById<TextView>(R.id.image_desc).text = "Other exception :("
e.printStackTrace()
}
}
As soon as i send the image, image_desc textView's text change to Image contains: null. I'm sure the server isn't the problem, since when I test it with this python code:
import requests
url=...
files = {'file': open('./test/cat.jpg', 'rb')}
r=requests.post(url,files=files)
print (r.text)
I get the desired response after a short delay. I've tried turning sendImage to a suspend func and writing job.join() but that crashes the app. How should fix this?
Try next code:
val job = GlobalScope.launch(Dispatchers.Main) {
val postOperation = async(Dispatchers.IO) { // <- extension on launch scope, launched in IO dispatcher
// blocking I/O operation
post(SERVER_URL, files = listOf(File(path).fileLike(name = "Image.jpg")))
}
response = postOperation.await() // wait for result of I/O operation without blocking the main thread
findViewById<TextView>(R.id.image_desc).text = "Image contains: ${response?.text}"
}
Also add next line to app's build.gradle dependency:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
Note that GlobalScope is discouraged to use, to launch a coroutine use an instance of CoroutineScope, or existing instance like viewModelScope or lifecycleScope.
UPDATE:
The correct approach would be to use lifecycleScope in Activity:
lifecycleScope.launch { // uses Dispatchers.Main context
val response = withContext(Dispatchers.IO) { // change context to background thread
// blocking I/O operation
post(SERVER_URL, files = listOf(File(path).fileLike(name = "Image.jpg")))
}
findViewById<TextView>(R.id.image_desc).text = "Image contains: ${response?.text}"
}