Search code examples
androiduriandroid-jetpack-compose

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


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


Solution

  • 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.