{"record":{"id":"7b58f423a37772cd","repo":"immich-app/immich","slug":"could-not-read-image-data-for-assetid","errorCode":null,"errorMessage":"Could not read image data for $assetId","messagePattern":"Could not read image data for \\$assetId","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"mobile/android/app/src/main/kotlin/app/alextran/immich/images/LocalImagesImpl.kt","lineNumber":190,"sourceCode":"          request.callback(CANCELLED)\n        } catch (_: Exception) {\n        }\n      }\n    }\n  }\n\n  private fun getEncodedImageInternal(\n    assetId: String,\n    callback: (Result<Map<String, Long>?>) -> Unit,\n    signal: CancellationSignal\n  ) {\n    signal.throwIfCanceled()\n    val id = assetId.toLong()\n    val uri = ContentUris.withAppendedId(Images.Media.EXTERNAL_CONTENT_URI, id)\n\n    signal.throwIfCanceled()\n    val bytes = resolver.openInputStream(uri)?.use { it.readBytes() }\n      ?: throw IOException(\"Could not read image data for $assetId\")\n\n    signal.throwIfCanceled()\n    val pointer = NativeBuffer.allocate(bytes.size)\n    try {\n      val buffer = NativeBuffer.wrap(pointer, bytes.size)\n      buffer.put(bytes)\n      signal.throwIfCanceled()\n      callback(Result.success(mapOf(\n        \"pointer\" to pointer,\n        \"length\" to bytes.size.toLong()\n      )))\n    } catch (e: Exception) {\n      NativeBuffer.free(pointer)\n      throw e\n    }\n  }\n\n  private fun getThumbnailBufferInternal(","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/mobile/android/app/src/main/kotlin/app/alextran/immich/images/LocalImagesImpl.kt#L172-L208","documentation":"getEncodedImageInternal in LocalImagesImpl.kt opens a MediaStore content InputStream for the given asset ID and reads all bytes. If openInputStream returns null (the provider cannot open the URI) it throws IOException 'Could not read image data for $assetId', which propagates to the Flutter side as a load failure for that thumbnail.","triggerScenarios":"The asset ID no longer exists in MediaStore (deleted or moved), the URI is unresolvable, or the content provider denies the stream for a cloud-backed/offline item.","commonSituations":"Thumbnailing assets that were deleted on the device since the last sync; backups restored to a device where local files are cloud placeholders; missing READ_MEDIA_IMAGES permission on newer Android versions.","solutions":["Re-query MediaStore for the asset ID; treat this asset as stale and remove it from the local listing.","Verify storage/photo permissions (READ_MEDIA_IMAGES / READ_EXTERNAL_STORAGE) are granted.","Check the file is not a cloud-only placeholder — trigger download or skip until available.","Re-run a full media sync so deleted assets are pruned before requesting thumbnails."],"exampleFix":"// before\nfinal bytes = await LocalImagesApi.getEncodedImage(assetId);\n// after\ntry {\n  bytes = await LocalImagesApi.getEncodedImage(assetId);\n} catch (e) {\n  await removeFromLocalIndex(assetId); // stale MediaStore entry\n}","handlingStrategy":"try-catch","validationCode":"// Dart: confirm asset still exists locally before requesting bytes\nfinal exists = await localApi.assetExists(assetId); // re-query MediaStore","typeGuard":null,"tryCatchPattern":"try {\n  bytes = await api.getEncodedImage(assetId);\n} catch (e) {\n  markAssetMissing(assetId); // prune stale entry, continue sync\n}","preventionTips":["Prune deleted assets from the local index before thumbnailing.","Verify READ_MEDIA_IMAGES permission at runtime.","Skip cloud-placeholder assets until downloaded."],"tags":["android","mediastore","io","image","content-provider"],"backgroundTag":"file-read-failed","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}