简体   繁体   中英

How to load an image using a URI stored in Room as a String

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 KK (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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM