I'm using Activity Result API (with FileProvider) to save image from Camera to app cache folder and then pass it URI to another activity.
It works if activity orientation does not change during operation.
But when user rotate Camera activity (launched by ActivityResultContracts.TakePicture contract) - image will not be saved (file created with 0 bytes). And I'm getting error while decoding image in activity where I'm pass URI:
android.graphics.ImageDecoder$DecodeException: Failed to create image decoder with message 'unimplemented'Input contained an error.
I tried to solve problem in obvious way - programmatically lock orientation before camera callback launch and unlock after, but this only works the first time (may sound strange, but that's how it is), when orientation is changed again - problem persists. It looks like after changing orientation connection between current activity result callback and the Camera activity is broken. After research, it turned out that if I do NOT launch activity to open image - image is correctly saved. I tried to launch activity with delay and in another thread - but without success.
I still can't find reason why image doesn't save when orientation changed.
Main activity:
class MainActivity : AppCompatActivity() {
private val tmpImageUri by lazy { FileUtils.getTmpFileUri(applicationContext) }
private val takeImageResult = registerForActivityResult(ActivityResultContracts.TakePicture()) { isSuccess ->
if (isSuccess) {
// We have successfully (actually no) saved the image and can work with it via tmpImageUri
launchEditActivity(tmpImageUri)
}
}
private fun takePictureFromCamera() {
takeImageResult.launch(tmpImageUri)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.button.setOnClickListener {
takePictureFromCamera()
}
}
private fun launchEditActivity(uri: Uri){
val intent = Intent(this, EditActivity::class.java).apply {
data = uri
}
startActivity(intent)
}
}
Activity where I'm pass URI:
class EditActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadImage()
}
private fun loadImage(){
intent.data?.let { uri ->
model.viewModelScope.launch(Dispatchers.IO) {
val bitmap = FileUtils.loadBitmapFromUri(applicationContext, uri)
runOnUiThread {
model.setImage(bitmap)
}
}
}
}
}
File utils:
fun getTmpFileUri(context: Context): Uri {
clearCache(context)
val tmpFile = File.createTempFile("tmp", ".jpg", context.cacheDir)
return FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", tmpFile)
}
private fun clearCache(context: Context){
context.cacheDir.deleteRecursively()
}
fun loadBitmapFromUri(context: Context, uri: Uri): Bitmap{
val bitmap: Bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val src = ImageDecoder.createSource(context.contentResolver, uri)
ImageDecoder.decodeBitmap(src) { decoder, info, source ->
decoder.isMutableRequired = true
}
} else {
MediaStore.Images.Media.getBitmap(context.contentResolver, uri)
}
return bitmap
}
(file created with 0 bytes)
But that empty file is not created by the camera app but by your -wrong- code usingcreateNewFile()
. Do not create the file already. You only need that File instance with the right path. – HappygoluckyAnd I'm getting error while decoding image in activity where I'm pass URI:
You could simply check existence of file and file size before calling that statement. – HappygoluckytmpImageUri
is "reset" (as new instance of MainActivity is created) - you should use obvious way and store previous uri in "savedInstance" – Chymotrypsinfirst starting another activity inside onCreate without finishing current is terible idea
May be. But i do not see that in the code. Its done in on activity result. @Selvin. – HappygoluckyWhich Android version of used device?
– Happygoluckyhave updated source code in question.
?? There is still File.createTmpFile(). – HappygoluckycreateNewFile()
anddeleteOnExit()
inFile.createTempFile("tmp", ".jpg", context.cacheDir)
– ScouringsMainActivity
is created yourFileUtils.getTmpFileUri
is called and old temp file is deleted and new one is created ... you need to savetmpImageUri
somewhere and then get it back when activity is recreated - I would usesavedInstanceState
for this – ChymotrypsinActivityResultContracts.TakePicture
should use differentActivityResultCallback
which not only returns boolean but also passed uri back... – Chymotrypsinval tmpFile = File(context.cacheDir, "tmp.jpg")
would be better. – HappygoluckytmpImageUri
was being reset when the activity rotated. After saving and restoring it in "savedInstance" everything started working as it should. Please post your answer so I can accept it. – Scourings