i am wanting to grab the image uri, upload it to firebase in order for it to be available there and other users r able to see this image when connecting to the firebase database. only issue is when an image is selected, firstImage is set as content://com.android.providers.media.documents/document/image%3A525 which doesn't have the extension, whether it's .png, etc.
the logic below is done in a composable function which is y something like gallery intent isn't used.
any insights?
var firstImage by remember { mutableStateOf<Uri?>(null) }
val firstLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) { uri ->
uri?.let { firstImage = it }
}
Related
As part of my app, users can select images from their device and I store the URLs to these in my database. Then, elsewhere in the app, I need to display those images.
I am getting the image URI through Intent.ACTION_PICK:
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
imagePickerAction = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
if (uri != null) {
val uriString = uri.toString()
saveToDatabase(uriString)
}
}
This results in a URI string like content://com.android.providers.media.documents/document/image%3A1000000038.
I am able to show the image using ImageView.setImageURI at this point.
But later on, when I fetch the URI from the database and try to use setImageURI again, I get this error:
java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{cdd4e32 10499:com.skamz.shadercam/u0a160} (pid=10499, uid=10160) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
I have also tried following the docs here https://developer.android.com/training/data-storage/shared/documents-files#open:
private fun getBitmapFromUri(uri: Uri): Bitmap {
val parcelFileDescriptor: ParcelFileDescriptor? =
contentResolver.openFileDescriptor(uri, "r")
if (parcelFileDescriptor == null) {
return bitmapFromUri(contentResolver, Uri.parse(TextureOverlayShaderData.defaultImageUrl))
}
val fileDescriptor: FileDescriptor = parcelFileDescriptor.fileDescriptor
val image: Bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor)
parcelFileDescriptor.close()
return image
}
This returns a bitmap, which I then pass into ImageView.bitmapFromUri. Unfortunately, this produces the same error, this time coming from contentResolver.openFileDescriptor(uri, "r")
when I fetch the URI from the database
That's not an option with ACTION_PICK. You cannot reliably persist a Uri that you received from ACTION_PICK and use it again later. Your access rights to the content are short-lived.
If you switch to ACTION_OPEN_DOCUMENT and use takePersistableUriPermissions() on a ContentResolver when you get the Uri in onActivityResult(), you can safely persist that Uri. You might still run into problems using it later — for example, the user could delete the image that the Uri points to — but you will not encounter the access problem that you are seeing here.
I am making a practice application to load the inventory of a store, inside the screen I press a floating button that generates a dialog that asks for an image among several data, which the user selects from their gallery, later when pressing the save button in the dialog, the image and the rest of the data are saved in the ViewModel and ROOM, to then generate an item on the main screen that shows these data at the same time that they are printed with Log.d
When generating the item after saving, it is shown correctly, however, if I restart the application the image disappears. Both when generating the image and when restarting the application, the Log.d print shows the same URI in both cases.
My main goal is to save an image, its address, or URI in ROOM, and then load the item with the image. My research leads me to believe that it is correct to save the URI as a string, but I am unaware of the proper practices for saving an image and I am open to other ways to reach a solution if necessary.
First, in a dialog to create an item, I select an image from the gallery like this and save it in the ViewModel and later in ROOM:
val singlePhotoPickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickVisualMedia(),
onResult = { uri ->
selectedImageUri = uri
Log.d("URI RESULT", "$uri")
viewModel.onDialogChanged(
uri.toString()
)
}
)
I save it in ROOM when I press the 'Save' button:
DialogButton(
ButtonDefaults.buttonColors(
backgroundColor = verdeBillete,
contentColor = Color.White
), "Guardar", modifier = Modifier, onClick = {
viewModel.viewModelScope.launch {
viewModel.onAddSelected(
inventoryItem(
0,
uri,
)
)
}
onDismiss()
})
//add to ViewModel
fun onAddSelected(addItem: inventoryItem) {
viewModelScope.launch {
addItem(addItem)
getInventory()
}
}
//ROOM Table
#Entity(tableName = "inventory")
data class inventoryItem(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "r_id")
var id: Int = 0,
#ColumnInfo(name = "r_uri")
val uri: String,
)
Then I currently try to load the image like this:
Log.d("Loading items", item.uri)
AsyncImage(
model = Uri.parse(item.uri),
contentDescription = null,
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.Crop
)
Just after selecting the image from the gallery, the image is visible, however, after restarting the application the image disappears. In both cases the printed URI in Log.d is the same.
Also, I have permission for:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
Update: After reading both answers from CommonsWare and Gowtham K K (Thank you both!) and trying to implement them, I couldn't write the code myself, so I entered the content of the post (the question and both answers) into chatgpt and asked for a solution, which presented me with the following solution which worked for me.
To use takePersistableUriPermission, you must do the following:
First, you need to have permissions to read or write the URI that you
want to save persistently. You can do this by adding the following
line of code in your AndroidManifest.xml file:
or
Then, you need to obtain the URI that you want to save persistently.
For example, if you want to save the URI of an image selected from the
gallery, you can use the ActivityResultContracts.PickVisualMedia
method as follows:
val singlePhotoPickerLauncher =
rememberLauncherForActivityResult( contract =
ActivityResultContracts.PickVisualMedia(), onResult = { uri ->
selectedImageUri = uri } )
Once you have the URI, you can use takePersistableUriPermission to
save it persistently. The takePersistableUriPermission method should
be used on the ContentResolver and takes two parameters: the URI and
the access mode (read or write). For example:
contentResolver.takePersistableUriPermission(uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION) or
contentResolver.takePersistableUriPermission(uri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
Finally, you can save the URI in your ROOM database as a text string
and load it in your application when necessary. For example:
val inventoryItem = inventoryItem(0, uri.toString())
viewModel.onAddSelected(inventoryItem)
Putting everything together:
var selectedImageUri by remember {
mutableStateOf<Uri?>(null)
}
val singlePhotoPickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickVisualMedia(),
onResult = { uri ->
selectedImageUri = uri
Log.d("URI RESULT", "$uri")
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION//or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
val resolver = mContext.contentResolver
resolver.takePersistableUriPermission(uri!!, flags)
viewModel.onDialogChanged( //**save to database function**
uri.toString()
)
}
)
This is because the URI would get revoked when app process get killed.
For Storage access framework URIs you can get long term permission using takePersistableUriPermission .
But It might not work for ActivityResultContracts.PickVisualMedia() as far as I know.
In your case you can make your own copy of the image after getting first time and save it in the app specific storage and save that URI in your db.
This will be more flexible. Even if original file gets deleted / you will still can get access of your copied image.
I am using an action chooser intent to ask the user to choose one of the following from a fragment:
MediaStore.ACTION_IMAGE_CAPTURE
MediaStore.ACTION_VIDEO_CAPTURE
Intent.ACTION_GET_CONTENT
I want to be able to distinguish the selected action of the user because I have different functions per action.
Below is my current code.
private val intentLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
//Identify the intent selected
//TODO: image from camera
//TODO: video from camera
//TODO: any file
}
}
private fun dispatchActionChooserIntent() {
Intent(Intent.ACTION_CHOOSER).also { actionChooserIntent ->
val cameraIntent = createCameraIntent(MediaStore.ACTION_IMAGE_CAPTURE)
val videoIntent = createCameraIntent(MediaStore.ACTION_VIDEO_CAPTURE)
val filePickerIntent = createFilePickerIntent()
actionChooserIntent.putExtra(Intent.EXTRA_INTENT, filePickerIntent);
actionChooserIntent.putExtra(
Intent.EXTRA_INITIAL_INTENTS,
arrayOf<Intent>(cameraIntent, videoIntent)
);
cameraIntent.putExtra("intentAction",Intent.ACTION_CHOOSER)
actionChooserIntent.putExtra(Intent.EXTRA_TITLE, "")
}
}
private fun createFilePickerIntent(fileType: String = "*/*"): Intent {
return Intent(Intent.ACTION_GET_CONTENT).also { filePickerIntent ->
filePickerIntent.type = fileType
filePickerIntent.addCategory(Intent.CATEGORY_OPENABLE)
filePickerIntent.resolveActivity(
(activity as AppCompatActivity).applicationContext.packageManager)
}
}
private fun createCameraIntent(cameraAction: String): Intent {
return Intent(cameraAction).also { cameraIntent ->
// Ensure that there's a camera activity to handle the intent
cameraIntent.resolveActivity(
(activity as AppCompatActivity).applicationContext.packageManager)
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraIntentURI)
}
}
the result only includes the resultCode and the data
Sample result from taking a photo
Sample result from taking a video
Sample result from choosing a file
If the received uri is null the camera was choosen.
The MediaStore.ACTION_VIDEO_CAPTURE honors Intent.EXTRA_OUTPUT and you will use your fileprovider there.
In contrary to the camera action you will get the supplied uri back. (Google has learned something).
So if you receive an uri in onActivityResult you can check for uri.getAuthority().
if the authority is the same as your fileprovider you know it is from video capture.
There can be two solutions.
Android way
When you receive the URI as data as a result. Process that URI and try to get the mime type. Based on mime type you can understand what type of data you have received. Check how it can be done here https://developer.android.com/training/secure-file-sharing/retrieve-info
Logical way
Convert the Uri into an instance of type File. Get the extension of a file and check what type of extension it is.
jpeg, png, etc fall into the image category, mp4 kind of extension is a video and the rest can be any other file. Try this to get the file extension https://www.tutorialkart.com/kotlin/kotlin-get-file-extension/#:~:text=Get%20File%20Extension%20in%20Kotlin,extension%20is%20a%20String%20value.
I would recommend exploring option 1. That's the right and safe approach.
Good luck!
I am taking a photo using the camera in Android Studio and I would like to save the actual image that resulted from the action. I can access the URI just fine but I would like the actual image itself, as I need to send the photo to a database.
var image_uri: Uri? = null
lateinit var bitmap: Bitmap
private fun openCamera() {
val resolver = requireActivity().contentResolver
val values = ContentValues()
values.put(MediaStore.Images.Media.TITLE, "New Picture")
values.put(MediaStore.Images.Media.DESCRIPTION, "From the Camera")
image_uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
bitmap = MediaStore.Images.Media.getBitmap(resolver, image_uri)
val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, image_uri)
startActivityForResult(cameraIntent, IMAGE_CAPTURE_CODE)
}
I have read that the easiest way to do this is to create a bitmap but I can not get that to work. Running my overall program, the application crashes whenever openCamera is even called. If I comment out the bitmap line, then the function works fine (except I don't have the file saved like I want). How can I do this to where bitmap is an actual Bitmap Object that I can send to the backend of my program?
You can get image bitmap from Camera with this way:
// Open camera
val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
resultLauncher.launch(cameraIntent)
// Get your image
private val resultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
if (result?.data != null) {
bitmap = result.data?.extras?.get("data") as Bitmap
}
}
}
Easiest way to get the Bitmap is in onActivityResult() like val imageBitmap = data.extras.get("data") as Bitmap. I suggest looking at the documentation for camera, maybe you'll find something useful here.
The way to get the actual image would be to pass the file object, you want to store the image at, to the intent - and that is where the full size image will be.
according to android developers documentation
you should create the file (assuming you've got the READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions depending on the android version and the location of the file you create...) and then pass the file to intent
private fun dispatchTakePictureIntent() {
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
// Ensure that there's a camera activity to handle the intent
takePictureIntent.resolveActivity(packageManager)?.also {
// Create the File where the photo should go
val photoFile: File? = try {
createImageFile()
} catch (ex: IOException) {
// Error occurred while creating the File
...
null
}
// Continue only if the File was successfully created
photoFile?.also {
val photoURI: Uri = FileProvider.getUriForFile(
this,
"com.example.android.fileprovider",
it
)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
}
}
}
}
In the code snippet it refers to a method "createImageFile()" where the file is being created (The docs in the link provides some samples).
I've tried various solutions on SO for this problem, but so far not one of them has worked :/
I'm letting users pick a photo from a gallery, have the app display a preview, save the image with a caption to a Room DB and then display multiple images in a RecyclerView using Google's Material Design Card Views.
Picking an image and showing it in the preview is not a problem. But displaying the images in the RecyclerView using Picasso is not working. It only shows my error image (broken image vector).
The following code is used to pick an image from the gallery and display the preview image:
private fun choosePicture() {
var chooseFile = Intent(Intent.ACTION_GET_CONTENT)
chooseFile.type = MIME_TYPE_IMAGE
chooseFile = Intent.createChooser(chooseFile, resources.getString(R.string.choose_file))
startActivityForResult(chooseFile, PICK_FILE_RESULT_CODE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
PICK_FILE_RESULT_CODE -> if (resultCode == -1) {
data!!.data?.let { returnUri ->
loadPicture(returnUri)
binding.constraintLayout.visibility = View.VISIBLE
viewModel.imagePath.value = returnUri
}
}
}
}
private fun loadPicture(uri: Uri) {
Picasso.get()
.load(uri)
.error(R.drawable.ic_broken_image_gray_24dp)
.into(binding.placeImage)
}
In my ViewHolder I'm using the following code to display the image (image path saved in Room DB as String):
fun bind(location: Location) {
textViewLocationName.text = location.name
Picasso.get()
.load(Uri.parse(location.imagePath!!))
.error(R.drawable.ic_broken_image_gray_24dp)
.into(imageViewLocation)
}
When debugging the URI looks like this: content://com.android.providers.media.documents/document/image%3A4569
Is there some kind of formatting issue with the URI? Or is there another problem?
Thanks!
Just in case anyone else has this problem, here's my solution:
Change the intent from ACTION_GET_CONTENT to ACTION_OPEN_DOCUMENT
Set the following flags for the intent:
chooseFile.flags = (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
In onActivityResult use the following line of code before using the URI for anything else:
context?.contentResolver?.takePersistableUriPermission(returnUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)