https://kotlinlang.org logo
#compose-web
Title
# compose-web
g

Giorgi

01/24/2023, 2:46 PM
Hi. I want to trigger a file select dialog from compose multiplatform. How can it be done? So I have a small code like this
Copy code
fun main() {
    onWasmReady {
        Window {
            var selectedDestination by remember { mutableStateOf(Destinations.HISTORY) }
            val destinations = Destinations.values()
            Row {
                NavigationRail {

                    FloatingActionButton(onClick = {
                        // TODO show file dialog here
                    }, shape = RoundedCornerShape(10.dp)) {
                        Icon(Icons.Filled.Add, contentDescription = "Add")
                    }

                    Spacer(Modifier.height(16.dp))

                    destinations.forEach { item ->
                        NavigationRailItem(
                            icon = { Icon(item.icon, contentDescription = item.title) },
                            label = { Text(item.title) },
                            selected = selectedDestination == item,
                            onClick = { selectedDestination = item }
                        )
                    }
                }
                when (selectedDestination) {
                    Destinations.HISTORY -> HistoryPage()
                    Destinations.STATS -> StatsPage()
                    Destinations.DOWNLOADS -> DownloadsPage()
                    Destinations.SETTINGS -> SettingsPage()
                }
            }
        }
    }
}
and when user clicks the
FloatingActionButton
he should be able to select a file. I tried to do it like here https://stackoverflow.com/a/8385882/4885394 but im not able to translate Javascript to Kotlin. How can I create an Input element and then add a click listener? tried like
document.createElement("input")
but then it has different type and I dont know to what I should cast it. Before I had this
Copy code
Input(InputType.File) {
                onChange {
                    val file = it.target.files?.asList()?.first()

                    if (file != null) {
                        scope.launch {
                            output = try {
                                YoutubeHistory(file.text(), minVideoClicks).toString()
                            } catch (e: Exception) {
                                e.message ?: "Unknown Error"
                            }
                        }
                    }
                }
            }
and it worked. Thats what I want to achieve but in actual multiplatform compose. Or what do you guys call it? the version that uses skia on web with canvas
d

David Herman

01/24/2023, 4:06 PM
You mention compose multiplatform here, but if you're asking just about Compose for Web, I wrote these utility methods in my own code:
Copy code
fun Document.downloadFileToDisk(
    filename: String,
    type: String,
    content: String,
) {
    val snapshotBlob = Blob(arrayOf(content), BlobPropertyBag(type))
    val url = DomURL.createObjectURL(snapshotBlob)
    val tempAnchor = (createElement("a") as HTMLAnchorElement).apply {
        style.display = "none"
        href = url
        download = filename
    }
    document.body!!.append(tempAnchor)
    tempAnchor.click()
    DomURL.revokeObjectURL(url)
    tempAnchor.remove()
}

fun Document.loadFileFromDisk(
    accept: String,
    onLoaded: (String) -> Unit,
) {
   val tempInput = (createElement("input") as HTMLInputElement).apply {
        type = "file"
        style.display = "none"
        this.accept = accept
        multiple = false
    }

    tempInput.onchange = { changeEvt ->
        val file = changeEvt.target.asDynamic().files[0] as File

        val reader = FileReader()
        reader.onload = { loadEvt ->
            val content = loadEvt.target.asDynamic().result as String
            onLoaded(content)
        }
        reader.readAsText(file, "UTF-8")
    }

    body!!.append(tempInput)
    tempInput.click()
    tempInput.remove()
}
which you'd call like so:
Copy code
document.loadFileFromDisk(".sav") { saveFileContent -> ... }
(I extended
Document
here because I try to avoid creating globally scoped methods when possible, but
document
itself is a global so technically my save/load methods can be global too). (Also, you didn't ask about downloading, but I included it in case it's helpful for anyone else who stumbles upon this later)
If you need this to work in other domains, e.g. Compose Desktop, you'd have to find out a way to extract some sort of common API, maybe create something like an expect
FileDialog
class with a
load
method in it, and then different constructors per platform to pass in anything you need to launch the dialog.
g

Giorgi

01/24/2023, 5:31 PM
Thank a lot. Even though I called it from a multiplatform widget, it worked perfectly, without any editing
nice nice
🎉
386 Views