如何使用在Room中存储为字符串的URI加载图像

huangapple go评论81阅读模式
英文:

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

问题

我正在制作一个练习应用程序,用于加载商店的库存。在屏幕上,我按下一个浮动按钮,它生成一个对话框,要求用户从他们的相册中选择一张图像,以及其他一些数据。然后,当在对话框中按下保存按钮时,图像和其他数据都保存在ViewModel和ROOM中,然后在主屏幕上生成一个项目,同时将这些数据打印在Log.d中显示出来。

在保存后生成项目时,它显示正确,但是,如果我重新启动应用程序,图像会消失。在生成图像和重新启动应用程序时,Log.d打印显示的URI是相同的。

我的主要目标是将图像及其地址或URI保存在ROOM中,然后加载带有图像的项目。我的研究让我相信,将URI保存为字符串是正确的做法,但是我不知道保存图像的适当做法,如果有必要,我也愿意尝试其他解决方法。

首先,在创建项目的对话框中,我像这样从相册中选择图像并将其保存在ViewModel中,然后保存在ROOM中:

val singlePhotoPickerLauncher = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.PickVisualMedia(),
    onResult = { uri ->
        selectedImageUri = uri
        Log.d("URI RESULT", "$uri")
        viewModel.onDialogChanged(uri.toString())
    }
)

当我按下“保存”按钮时,将其保存在ROOM中:

DialogButton(
    ButtonDefaults.buttonColors(
        backgroundColor = verdeBillete,
        contentColor = Color.White
    ),
    "Guardar",
    modifier = Modifier,
    onClick = {
        viewModel.viewModelScope.launch {
            viewModel.onAddSelected(
                inventoryItem(
                    0,
                    uri
                )
            )
        }
        onDismiss()
    }
)

// 添加到ViewModel
fun onAddSelected(addItem: inventoryItem) {
    viewModelScope.launch {
        addItem(addItem)
        getInventory()
    }
}

// ROOM表
@Entity(tableName = "inventory")
data class inventoryItem(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "r_id")
    var id: Int = 0,
    @ColumnInfo(name = "r_uri")
    val uri: String,
)

然后,我尝试加载图像如下:

Log.d("Loading items", item.uri)
AsyncImage(
    model = Uri.parse(item.uri),
    contentDescription = null,
    modifier = Modifier.fillMaxWidth(),
    contentScale = ContentScale.Crop
)

在从相册中选择图像后,图像可见,但是在重新启动应用程序后,图像会消失。在这两种情况下,Log.d中打印的URI都是相同的。

此外,我已经获取了以下权限:

<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" />

更新:在阅读CommonsWare和Gowtham K K的答案后(谢谢你们两个!)并尝试实施它们后,我无法自己编写代码,因此我将帖子的内容(问题和两个答案)输入到chatgpt中,要求提供解决方案,chatgpt为我提供了以下解决方案,对我有效。

要使用takePersistableUriPermission,您必须执行以下步骤:

首先,您需要具有读取或写入要保存持久的URI的权限。您可以在AndroidManifest.xml文件中添加以下代码行来实现这一点:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

然后,您需要获取要保存持久的URI。例如,如果要保存从图库中选择的图像的URI,可以使用ActivityResultContracts.PickVisualMedia方法如下:

val singlePhotoPickerLauncher = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.PickVisualMedia(),
    onResult = { uri ->
        selectedImageUri = uri
    }
)

一旦您有了URI,就可以使用takePersistableUriPermission 来持久保存它。takePersistableUriPermission 方法应该在ContentResolver上使用,并接受两个参数:URI和访问模式(读取或写入)。例如:

contentResolver.takePersistableUriPermission(uri,
    Intent.FLAG_GRANT_READ_URI_PERMISSION)

contentResolver.takePersistableUriPermission(uri,
    Intent.FLAG_GRANT_WRITE_URI_PERMISSION)

最后,您可以将URI保存在您的ROOM数据库中作为文本字符串,并在需要时在应用程序中加载它。例如:

val inventoryItem = inventoryItem(0, uri.toString())
viewModel.onAddSelected(inventoryItem)

将所有这些组合在一起:

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
        val resolver = mContext.contentResolver
        resolver.takePersistableUriPermission(uri!!, flags)
        viewModel.onDialogChanged(uri.toString())
    }
)
英文:

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 -&gt;
selectedImageUri = uri
Log.d(&quot;URI RESULT&quot;, &quot;$uri&quot;)
viewModel.onDialogChanged(
uri.toString()
    )
  }
)

I save it in ROOM when I press the 'Save' button:


DialogButton(
ButtonDefaults.buttonColors(
backgroundColor = verdeBillete,
contentColor = Color.White
), &quot;Guardar&quot;, 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 = &quot;inventory&quot;)
data class inventoryItem(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = &quot;r_id&quot;)
var id: Int = 0,
@ColumnInfo(name = &quot;r_uri&quot;)
val uri: String,
)

Then I currently try to load the image like this:

Log.d(&quot;Loading items&quot;, 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:

 &lt;uses-permission android:name=&quot;android.permission.READ_EXTERNAL_STORAGE&quot;/&gt;
    &lt;uses-permission android:name=&quot;android.permission.READ_MEDIA_IMAGES&quot; /&gt;
    &lt;uses-permission android:name=&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot;
        tools:ignore=&quot;ScopedStorage&quot; /&gt;

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:
>
> <uses-permission
> android:name="android.permission.READ_EXTERNAL_STORAGE"/> or
>
> <uses-permission
> android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
>
> 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 -&gt;
    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&lt;Uri?&gt;(null)
    }

    val singlePhotoPickerLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.PickVisualMedia(),
        onResult = { uri -&gt;
            selectedImageUri = uri
            Log.d(&quot;URI RESULT&quot;, &quot;$uri&quot;)
            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()
            )
        }
    )

答案1

得分: 2

这是因为当应用程序进程被终止时,URI 将被撤销。

对于存储访问框架的 URI,您可以使用 takePersistableUriPermission 来获得长期权限。

但据我所知,对于 ActivityResultContracts.PickVisualMedia() 可能不起作用。

在您的情况下,您可以在第一次获取图像后制作自己的副本,并将其保存在应用程序特定的存储中,然后将该 URI 保存在您的数据库中。这将更加灵活。即使原始文件被删除,您仍然可以访问您复制的图像。

英文:

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.

huangapple
  • 本文由 发表于 2023年1月6日 15:04:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/75027931.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定