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
*/
}
}
}
}
Related
I am trying to read a String field that I recently updated. My function that should return the String, returns an empty String.
Here is my codes:
Function that returns the updated String:
fun readUsersOfficeHoursList(email: String, callback: (String) -> Unit) {
val database = FirebaseFirestore.getInstance()
val ref = database.collection("Users").document(email)
ref.get()
.addOnSuccessListener { document ->
if (document != null) {
val officeHoursList = document.get("office_hours_list") as String
callback(officeHoursList)
Log.d(TAG, "office_hours_list successfully read")
} else {
Log.d(TAG, "Is empty")
callback("")
}
}
.addOnFailureListener { exception ->
if (exception is FirebaseFirestoreException) {
Log.e(TAG, "Error getting document: ", exception)
}
callback("")
}
}
Function that updates the field:
fun updateUserOfficeHoursList(email: String, code: String){
val database = FirebaseFirestore.getInstance()
val ref = database.collection("Users").document(email)
var list = ""
ref.get()
.addOnSuccessListener { document ->
if (document != null) {
list = document.get("office_hours_list") as String? ?: ""
Log.d(TAG, "office_hours_list successfully read")
if(!list.contains(code)){
if (list.isEmpty()){
list = code
}
else{
list = "$list, $code"
}
ref.update("office_hours_list", list)
.addOnSuccessListener { Log.d(TAG, "List successfully updated") }
.addOnFailureListener { e -> Log.w(TAG, "Error updating list", e) }
}else{
Log.d(TAG, "code already in the list")
}
} else {
Log.d(TAG, "Is empty")
}
}
.addOnFailureListener { exception ->
if (exception is FirebaseFirestoreException) {
Log.e(TAG, "Error getting document: ", exception)
}
}
}
My test code:
myClass.updateUserOfficeHoursList("tfh#gmail.com", "1VVNFxSGbYaauk3iLV80,
1a79bhnaJsY5OhHwaYhH")
myClass.readUsersOfficeHoursList("tfh#gmail.com") {fieldValue1 ->
textView.text = fieldValue1
Log.d(TAG, "fieldValue1: $fieldValue1")
}
The error I get:
**2023-01-16 13:43:50.523 8529-8529/com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.myapplication, PID: 8529
java.lang.NullPointerException: null cannot be cast to non-null type kotlin.String
at com.example.myapplication.RepositoryMockup$readUsersOfficeHoursList$1.invoke(RespositoryMockup.kt:239)
at com.example.myapplication.RepositoryMockup$readUsersOfficeHoursList$1.invoke(RespositoryMockup.kt:237)
at com.example.myapplication.RepositoryMockup.readUsersOfficeHoursList$lambda$15(RespositoryMockup.kt:237)
at com.example.myapplication.RepositoryMockup.$r8$lambda$Rz1CeV4qQ243JiYTVZ8j2Ijj1y0(Unknown Source:0)
at com.example.myapplication.RepositoryMockup$$ExternalSyntheticLambda16.onSuccess(Unknown Source:2)
at com.google.android.gms.tasks.zzm.run(com.google.android.gms:play-services-tasks##18.0.1:1)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7898)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
**
This is the wrong way to check whether a document you tried to read exists:
if (document != null) {
When no document exists, the Firestore API returns a DocumentSnapshot (so not null) with its exists property set to false. So the correct check would be:
if (document.exists) {
So i found two methods that were suitable for simple reading, but as code became more complex "Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference" or "java.io.FileNotFoundException: 20210922: open failed: EROFS (Read-only file system)" appeared.
Here are two methods that i was trying to use.
private fun getJSONFromAssets(): String? {
var json: String? = null
val charset: Charset = Charsets.UTF_8
try {
val workerJSONFile = assets.open("users.json")
val size = workerJSONFile.available()
val buffer = ByteArray(size)
workerJSONFile.read(buffer)
workerJSONFile.close()
json = String(buffer, charset)
} catch (e: IOException) {
e.printStackTrace()
return null
}
return json
}
And
private fun getAdapter(): WorkerAdapter {
val nullWorker = WorkerModel(0, "0", "0", false)
val temp: MutableList<WorkerModel> = MutableList(1) { nullWorker }
val nullWorkerList = Workers(temp)
return try {
val bReader: BufferedReader = File("workers.json").bufferedReader()
val workers = Gson().fromJson(bReader.readText(), Workers::class.java)
WorkerAdapter(this, workers.workers, this) { show -> showDeleteItemMenu(show) }
} catch (e: IOException) {
e.printStackTrace()
WorkerAdapter(this, nullWorkerList.workers, this) { show -> showDeleteItemMenu(show) }
}
}
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.
I am using Kotlin and writing a function to upload a file. While testing, I have observed that if I do the following operations, OutOfMemory exception is raised and onFailure callback is called.
Select a file of size 100 MB to upload it to server. Upload it by calling uploadFile.
While it uploads, disconnect the internet. onFailure is called with an exception (timeout).
Enable the internet again and try to upload it again by calling uploadFile function.
Repeat the step 2 and 3 for 1-2 more times and app crashes. onFailure is called this time with OutOfMemory exception.
Here is my code.
class UploadManager(
private val fileTransferDataSource: IListFileTransferDataSource
) {
private val uploadClient by lazy {
OkHttpClient.Builder()
.retryOnConnectionFailure(false)
.writeTimeout(40, TimeUnit.SECONDS)
.readTimeout(40, TimeUnit.SECONDS)
.build()
}
suspend fun uploadFile(url: String,
fileUri: String,
downloadUrl: String?,
stream: InputStream,
callback: ((success: Boolean, filePath: String, url: String?, responseCode: Int?) -> Unit)? = null) {
val baseUrl = FileTransferUtility.getBaseUrl(url)
val authToken = fileTransferDataSource.getAuthenticationToken(baseUrl)
if (baseUrl.isEmpty() || authToken.isEmpty()) {
stream.close()
callback?.let { it(false, fileUri, null, null) }
return
}
kotlin.runCatching {
val buf = ByteArray(stream.available())
val bytesRead = stream.read(buf)
stream.close()
if (bytesRead == -1) {
callback?.let { it(false, fileUri, null, null) }
return#runCatching
}
val requestBody = create(FileTransferUtility.contentTypeStream.toMediaType(), buf)
val request = requestBody.let {
Request.Builder()
.url(url)
.post(it)
.addHeader(HttpConstants.Headers.AUTHORIZATION, HttpConstants.Values.AUTHORIZATION_TOKEN_BEARER_FORMAT.format(authToken))
.addHeader(HttpConstants.Headers.CONTENT_TYPE, HttpConstants.Values.APPLICATION_JSON)
.addHeader(HttpConstants.Headers.ACCEPT, HttpConstants.Values.APPLICATION_JSON)
.build()
}
if (request == null) {
callback?.let { it(false, fileUri, null, null) }
return#runCatching
}
uploadClient.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
call.cancel()
callback?.let { it(false, fileUri, null, null) }
}
override fun onResponse(call: Call, response: Response) {
response.body?.close()
callback?.let { it(response.code == 200, fileUri, downloadUrl, response.code) }
}
})
}
}
//Restricting object creation for this class by making it singleton
companion object : SingletonHolder<UploadManager, IListFileTransferDataSource>(::UploadManager)
}
However, it doesn't crash even if I upload multiple files of size 100 MB and they gets upload without any issue. Problem happens only when onFailure is triggered multiple times. I am suspecting the some internal buffer aren't getting deallocating on failure.
I have tired the following.
Cancelling the dispatcher
Add interceptor for Logging with log level None.
cancelling call explicitly in onFailure callback
Nothing seems to resolve this issue. Please help me to identify the memory leak.
Stack Trace:
java.io.IOException: canceled due to java.lang.OutOfMemoryError: Failed to allocate a 8208 byte allocation with 200 free bytes and 200B until OOM, target footprint 268435456, growth limit 268435456
0 = {StackTraceElement#16551} "okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:515)"
1 = {StackTraceElement#16552} "java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)"
2 = {StackTraceElement#16553} "java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)"
3 = {StackTraceElement#16554} "java.lang.Thread.run(Thread.java:919)"
I have realized the problem was with val buf = ByteArray(inputStream.available()). It creates a ByteArray of huge size in a single go. If we have to upload multiple files of larger size (in my case, 100MB), garbage collector take a while to free the memory from last uploads. It was causing the memory exception. I have changed my code and it stopped being a problem:
suspend fun uploadFile(
url: String,
fileUri: String,
downloadUrl: String?,
stream: InputStream,
coroutineScope: CoroutineScope
): Flow<Result> = flow {
kotlin.runCatching {
val baseUrl = FileTransferUtility.getBaseUrl(url)
val authToken = fileTransferDataSource.getAuthenticationToken(baseUrl)
if (baseUrl.isEmpty() || authToken.isEmpty()) {
stream.close()
emit(Result.Failure(fileUri))
return#flow
}
var connection: HttpURLConnection? = null
val bufferedInputStream = stream.buffered()
try {
val fileSize = stream.available()
if (fileSize == 0)
throw IOException("Unable to read file $fileUri")
connection = (URL(url).openConnection() as HttpURLConnection).also { conn ->
addHeadersForUpload(conn, authToken, fileSize)
conn.readTimeout = UPLOAD_READ_TIMEOUT
}
var progress = 0
emit(Result.InProgress(fileUri, progress))
BufferedOutputStream(connection.outputStream).use { uploadStream ->
var bytesWritten = 0
val buffer = ByteArray(DEFAULT_UPLOAD_BUFFER_SIZE)
while (true) {
val size = bufferedInputStream.read(buffer)
if (size <= 0)
break
bytesWritten += size
progress = ((100f * bytesWritten) / fileSize).toInt()
emit(Result.InProgress(fileUri, progress))
uploadStream.write(buffer, 0, size)
}
uploadStream.flush()
}
val responseCode = connection.responseCode
val isSuccess = responseCode == 200
if (isSuccess) {
if (progress < 100)
emit(Result.InProgress(fileUri, 100))
emit(Result.Success(fileUri, downloadUrl, readResponseBody(connection)))
} else {
emit(Result.Failure(fileUri))
}
} catch (e: Exception) {
emit(Result.Failure(fileUri))
} finally {
bufferedInputStream.close()
connection?.disconnect()
}
}
}.flowOn(Dispatchers.IO)
private fun readResponseBody(connection: HttpURLConnection): String? {
val builder = StringBuilder()
val lineBreak = System.getProperty("line.separator")
BufferedReader(connection.inputStream.reader()).use {
val line = it.readLine() ?: return#use
builder.append(line + lineBreak)
}
return builder.toString().trim(*lineBreak.toCharArray())
}
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.