displaying image using Uri -> from image picker works but from db room don't... (Jetpack Compose)
Asked Answered
A

3

8

I have a caller app that allows the user to pick up photos for the contacts and audio files for the ringtones from their mobile. When I pick up the photo, it can be displayed correctly using the URI on the interface. on both the main page and contact page. and i save it inside the room database...

if i close the app and reopen it, it gets the same URI from db. but no image is displayed. neither on the main page or contact page. i tried to insert the URI code manually it didn't display anything as well... also change the image loader to GlideImage from rememberImagePainter (another implementation) but same issue... the URI i have from db and image picker looks like this val uri2:Uri = "content://com.android.providers.media.documents/document/image%3A34".toUri()

the code the pick up the image Uri is this

class GetContentActivityResult(
 private val launcher: ManagedActivityResultLauncher<String, Uri>,
       val uri: Uri?
          ) {
          fun launch(mimeType: String) {
           launcher.launch(mimeType)
        }
      }

@Composable
fun rememberGetContentActivityResult(): GetContentActivityResult {

 var uri by rememberSaveable { mutableStateOf<Uri?>(null) }

 val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent(), onResult = {

      uri = it
     })

 return remember(launcher, uri) { GetContentActivityResult(launcher, uri) }
}

i display the image  in the contact page like this

val getContent = rememberGetContentActivityResult() <-- to access the class of pickup files

if (roomViewModel.photoUri.value.isBlank()){ <-- if no image from db display the picked image using image picker 
    getContent.uri?.let {
              Image(
                modifier = Modifier
                             .align(Alignment.TopCenter)
                             .fillMaxSize(1f),
                         contentDescription = "UserImage",
                         contentScale = ContentScale.Crop,
                painter = rememberImagePainter(data = it))
                      }
         }else{

             Image(
               modifier = Modifier
                            .align(Alignment.TopCenter)
                            .fillMaxSize(1f),
                         contentDescription = "UserImage",
                         contentScale = ContentScale.Crop,
                         painter = rememberImagePainter(data = roomViewModel.photoUri.value))
            }
    }

           TextButton(
                      onClick = { getContent.launch("image/*") }, <-- launching the class to get the image URI by defining the Mime as image/*
                      modifier = Modifier.layoutId("changePhoto")
            ) {
     Text(
         text = "Change Photo",
        color = Color.Black)}

inside the main page. there is no image picker, i just display the data that i have in the Database, and i display the image this way

@OptIn(ExperimentalCoilApi::class)
@Composable
fun ProfilePicture(uri : Uri, imageSize: Dp) {
 Card(
     shape = CircleShape,
     border = BorderStroke(
     width = 2.dp,
     color = Color.Red ),
     modifier = Modifier.padding(16.dp),
     elevation = 4.dp) {

 val uri2:Uri = "content://com.android.providers.media.documents/document/image%3A34".toUri() //<-- i tried to load the image this way also it didn't work.

 val painter = rememberImagePainter(
                data = uri,
                builder = {
                placeholder(R.drawable.yara) <-- the placeholder image blinks quickly when app is loaded and then disappear...
     }
  )
    Image(
       painter = painter,
       contentDescription = "User Image",
       modifier = Modifier.size(imageSize) )
 }
}

Is it an issue with the Uri picker or image display?

other content from room db are displayed correctly (strings)

i tried to use the code on emulator and real device and it is the same for both cases...

one thing i just noticed... if i used the image picker and pick a previously picked image (it's uri is in the db) the image is displayed on the other locations in the app automatically without doing anything else... just picking the image and displaying it without saving it even...

Autoclave answered 8/9, 2021 at 10:8 Comment(7)
please format your code so it can be readableLandbert
better? i added a note at the end... do you think the issue is that i should use coroutines to load the images instead of this way?Autoclave
Do not save the Uri values in a database (or any other sort of file). Or, switch to OpenDocument instead of GetContent and use takePersistableUriPermission() to get durable access to the content. Otherwise, your rights to access the content will be short-lived.Sabbatical
When you pick an image with ActivityResultContracts.GetContent(), system will give you a temporary URL. It's fine to use it during one session, but if you wanna store it - you need to copy it into your app storageLandbert
I did saved it in room, and checked the returned result from the picker and it is the same Uri... is it about the takePersistableUriPermission() or to add a flag as the link suggests?Autoclave
---> using takePersistableUriPermissions() on ContentResolver to get long-term access to the content.Autoclave
No I think what they meant was that the URL would not refer to the same place for long. It is temporary and ok to use only for a single session.Carat
A
2

Well, I didn't know how to do the takePersistableUriPermission(), I'm still learning. But I created a converter class (the longest way solution) to use the Uri to convert it to bit map... but i have to make it drawable first. I didn't know how to do it bitmap from Uri directly.

I updated the data class in the first place

@Entity
data class Contact(
    @PrimaryKey()
    val id: UUID = UUID.randomUUID(),
    val name: String,
    val land_line : String,
    val mobile: String,
    val contact_photo: Bitmap
)

Added a TypeConverter for the bitmap in database

    @TypeConverter
    fun fromBitmap(bitmap: Bitmap): ByteArray {
        val outputStream = ByteArrayOutputStream()
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
        return outputStream.toByteArray()
    }

    @TypeConverter
    fun toBitmap(byteArray: ByteArray): Bitmap {
        return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
    }

Changed the values types all the way to UI, and created a converter class in the Util package to convert the Uri into drawable then Bitmap (I know it looks stupid - any better solution is accepted).

suspend fun convertToBitmap(uri: Uri, context: Context, widthPixels: Int, heightPixels: Int): Bitmap? {
    val mutableBitmap = Bitmap.createBitmap(widthPixels, heightPixels, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(mutableBitmap)
    val drawable = getDrawable(uri, context)
    drawable?.setBounds(0, 0, widthPixels, heightPixels)
    drawable?.draw(canvas)
    return mutableBitmap
}


@OptIn(ExperimentalCoilApi::class)
suspend fun getDrawable(uri: Uri, context: Context): Drawable? {
    val imageLoader = ImageLoader.Builder(context)
        .availableMemoryPercentage(0.25)
        .allowHardware(false)
        .crossfade(true)
        .build()

    val request= ImageRequest.Builder(context)
        .data(uri)
        .build()
    return imageLoader.execute(request).drawable
}

It is working now and loading perfectly. I know that I have the width and height by 50 50 px, but this is a test anyway.

Autoclave answered 8/9, 2021 at 16:31 Comment(2)
how do you use these four functions? I can't see any connection between themMunicipal
you should know Jetpack Room to get it...Autoclave
C
0

I've been working with Compose and I have a solution for this, so let me give you some context.

First, I have a notes app with CRUD transactions, I used room and dagger hilt. So in my composable AddNoteScreeen.kt is where I add or update notes, but this is the important part

val mediaPickerLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.PickVisualMedia(),
        onResult = { uri ->
            if (uri != null) {
                val flag = Intent.FLAG_GRANT_READ_URI_PERMISSION
                context.contentResolver.takePersistableUriPermission(uri, flag)
                noteViewModel.onImageChanged(uri)
            }

        }
    )

I declared this above surface composable, the first composable component. This piece of code says to Android that you're gonna make a launcher and wait for a result, the contract part says that you're gonna use a standard activity, that is PickVisualMedia(), and on that activity, you can pick only one element. I encourage you to check the docs here: Photo picker

Then, I check for my result, if it's not null I assign it to my state in my ViewModel. But before I declare a flag, this flag allows you to upload a large file in the background, you might need this access to persist for a longer period of time and it has to be associated with the URI that you picked.

Finally, you can launch this with the next code

 Button(onClick = {
                    mediaPickerLauncher.launch(
                        PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
                    )
                }) {
                    Text(text = "Select Image")
                }

Where PickVisualMediaRequest creates a request to launch that activity, in this part, you can declare what kind of resource you need to show, such as a video, image, or GIF.

With this process, I save the image using Room, obviously, this response is focused on the UI and how you save the elements.

Here is the GitHub repository

Creamer answered 29/11, 2023 at 23:23 Comment(0)
F
0

Add below code to your launcher:

val flag = Intent.FLAG_GRANT_READ_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flag)
Figueroa answered 9/2 at 10:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.