Skip to content
This repository has been archived by the owner on Jun 25, 2024. It is now read-only.

Exception on Android #107

Open
joreilly opened this issue Jan 14, 2024 · 8 comments · May be fixed by #109
Open

Exception on Android #107

joreilly opened this issue Jan 14, 2024 · 8 comments · May be fixed by #109

Comments

@joreilly
Copy link

I have code that's working fine in other Compose clients but on Android I'm getting following when calling getFileByteArray

01-14 19:37:45.657 22788 22788 E AndroidRuntime: java.lang.IllegalArgumentException: Uri lacks 'file' scheme: content://com.android.providers.media.documents/document/image%3A1000050843
01-14 19:37:45.657 22788 22788 E AndroidRuntime: 	at androidx.core.net.UriKt.toFile(Uri.kt:43)
01-14 19:37:45.657 22788 22788 E AndroidRuntime: 	at com.darkrockstudios.libraries.mpfilepicker.AndroidFile.getFileByteArray(AndroidFilePicker.kt:15)

following is code I have

    val coroutineScope = rememberCoroutineScope()

    val fileExtensions = listOf("jpg", "png")
    FilePicker(show = show, fileExtensions = fileExtensions) { file ->
        coroutineScope.launch {
            val data = file?.getFileByteArray()
            data?.let {
                ....
            }
        }
    }
@lusc8520
Copy link

lusc8520 commented Jan 17, 2024

I had the same problem and found a weird workaround
(inspired by #104)

I have a global variable in commainMain to store the byte array (this is not necessary, only if you want to do more than just displaying the picked image):

// this variable is set from "outside" in the jvmMain and androidMain
var imageBytes : ByteArray = byteArrayOf()

this is my commonMain part:

var showFilePicker by remember { mutableStateOf(false) }
        colorButton(onClick = {showFilePicker = true}, text = "Choose Image")
        var showImage by remember {mutableStateOf(false)}
        val fileType = listOf("jpg", "png")
        var file by remember { mutableStateOf<MPFile<Any>?>(null) }
        FilePicker(
            show = showFilePicker,
            fileExtensions = fileType,
            onFileSelected = {
                showFilePicker = false
                scope.launch {
                    file = it
                    showImage = true
                }
            }
        )
        if (showImage) {
            ImageFromFile(file)
        }

then in commonMain i have this expect function declared:

@Composable
expect fun ImageFromFile(file: MPFile<Any>?)

The actual implementation in androidMain:

@Composable
actual fun ImageFromFile(file: MPFile<Any>?) {
    var imageBitmap by remember { mutableStateOf(ImageBitmap(height = 1, width = 1)) }
    if (file != null) {
        val uri = Uri.parse(file.path)
        val stream = LocalContext.current.contentResolver.openInputStream(uri)
        if (stream != null) {
            val bytes = stream.readBytes()
            imageBytes = bytes // sets the global byteArray variable in commonMain
            stream.close()
            if (bytes.isNotEmpty()) {
                imageBitmap.prepareToDraw()
                imageBitmap = BitmapFactory.decodeByteArray(
                    bytes,
                    0,
                    bytes.size
                ).asImageBitmap()
            }
        }
    }
    Image(
        bitmap = imageBitmap,
        ""
    )
}

actual implementation in jvmMain:

@Composable
actual fun ImageFromFile(file: MPFile<Any>?) {
    var imageBitmap by remember { mutableStateOf(ImageBitmap(height = 1, width = 1)) }
    val scope = rememberCoroutineScope()
    scope.launch {
        if (file == null) return@launch
        imageBitmap.prepareToDraw()
        imageBytes = file.getFileByteArray() // sets the global byteArray variable in commonMain
        imageBitmap = Image.makeFromEncoded(file.getFileByteArray()).toComposeImageBitmap()
    }
    Image(
        bitmap = imageBitmap,
        ""
    )
}

Furthermore, I have these functions for displaying an Image from just a byte Array:

// commonMain expect function
@Composable
expect fun ImageFromByteArray(byteArray: ByteArray, modifier:Modifier = Modifier, scale: ContentScale = ContentScale.Fit)

// androidMain actual function
@Composable
actual fun ImageFromByteArray(byteArray: ByteArray, modifier: Modifier, scale: ContentScale) {
    var imageBitmap by remember { mutableStateOf(ImageBitmap(height = 1, width = 1)) }
    imageBitmap = BitmapFactory.decodeByteArray(byteArray,
        0,
        byteArray.size).asImageBitmap()
    Image(
        modifier = modifier,
        bitmap = imageBitmap,
        contentDescription = "",
        contentScale = scale
    )
}

// jvmMain actual function
@Composable
actual fun ImageFromByteArray(byteArray: ByteArray, modifier: Modifier, scale: ContentScale) {
    val scope = rememberCoroutineScope()
    var imageBitmap by remember { mutableStateOf(ImageBitmap(height = 1, width = 1)) }
    scope.launch {
        imageBitmap.prepareToDraw()
        imageBitmap = Image.makeFromEncoded(byteArray).toComposeImageBitmap()
    }
    Image(
        modifier = modifier,
        bitmap = imageBitmap,
        contentDescription = "",
        contentScale = scale
    )
}

@joreilly
Copy link
Author

Thanks @lusc8520 ....using adapted version of that now in https://github.com/joreilly/GeminiKMP/blob/main/composeApp/src/androidMain/kotlin/actual.kt

@vinceglb vinceglb linked a pull request Jan 24, 2024 that will close this issue
@Shahriyar13
Copy link
Contributor

Could you try this?:
https://stackoverflow.com/a/8370299/9133703

@randyheaton
Copy link

While folks are waiting on that pr, here's some of the guts of it worked as an extension function so that you can use the library as is. This just steals the main punchline from vinceglb's pr of using contentResolver to turn a Uri into a ByteArray. Assumes the original works correctly in iOS. Handling the non-null assertion and supplying the Android context is dealt with elsewhere.

Common:
expect suspend fun MPFile<Any>.getBytes(): ByteArray

iOS:
actual suspend fun MPFile<Any>.getBytes(): ByteArray { return this.getFileByteArray() }

Android:
actual suspend fun MPFile<Any>.getBytes(): ByteArray { return AndroidApplication.getApplicationContext().contentResolver.openInputStream(this.platformFile as Uri).use { stream -> stream!!.readBytes() } }

@c4software
Copy link

Thanks @randyheaton its works with your workaround.

@vinceglb Did you know if the PR is mergeable ?

@vinceglb
Copy link
Collaborator

vinceglb commented Mar 5, 2024

@c4software I sent a message to other maintainers, I'll let you know as soon as I have any news 👍

@vinceglb
Copy link
Collaborator

vinceglb commented Mar 7, 2024

@c4software I discuss with Wavesonics, it supposed to have a reviewer before merging. So, I'm waiting to a maintainer to review it. If it takes too long, I'll check with Wavesonics how to proceed.

@c4software
Copy link

Thanks for you feedback 👍

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants