Display asset text file in Composable function using Kotlin Jetpack Compose - kotlin

I'm trying to read a text file using Kotlin from my Assets folder and display it to a Compose text widget. Android Studio Arctic Fox 2020.3
The following code runs successfully and displays the text file to the Output console, however I can't figure out how to get the text file and pass it to a Compose text widget.
You'll notice that I have 2 text() calls inside ReadDataFile(). The first text() is outside of try{} and it works fine, but the text() inside the try{} causes an error: "Try catch is not supported around composable function invocations"
How can I make this work?
Thanks!
package com.learning.kotlinreadfile
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import com.learning.kotlinreadfile.ui.theme.KotlinReadFileTheme
import java.io.InputStream
import java.io.IOException
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KotlinReadFileTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
ReadDataFile()
}
}
}
}
}
#Composable
fun ReadDataFile() {
println("Read Data File")
Text("Read Data File")
val context = LocalContext.current
try {
val inputStream: InputStream = context.assets.open("data.txt")
val size: Int = inputStream.available()
val buffer = ByteArray(size)
inputStream.read(buffer)
var string = String(buffer)
println(string)
//Text(string) // ERROR: Try catch is not supported around composable function invocations
} catch (e: IOException) {
e.printStackTrace()
println("Error")
}
}

Caution
File read (I/O) operations can be long, so it is not recommended to use the UI scope to read files. But that's not what's causing the problem, I'm just warning you that if it reads very large files, it can make your app crash because it does a very long processing in the UI thread. I recommend checking this link if you're not familiar with this type of problem.
Problem solving following best practices
Fortunately Jetpack compose works very well with reactive programming, so we can take advantage of that to write reactive code that doesn't face the aforementioned problems.
I made an example very similar to yours, I hope you can understand:
UiState file
As stated earlier, reading a file can be a long process, so let's imagine 3 possible states, "loading", "message successful" and "error". In the case of "successful message" we will have a possibly null string that will no longer be null when the message is actually read from the txt file:
package com.example.kotlinreadfile
data class UiState(
val isLoading: Boolean,
val isOnError: Boolean,
val fileMessage: String?
)
MainActivity.kt file
Here will be just our implementation of the UI, in your case the text messages arranged in the application. As soon as we want to read these messages on the screen we will make a request to our ViewModel:
package com.example.kotlinreadfile
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.*
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.example.kotlinreadfile.ui.theme.KotlinReadFileTheme
class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KotlinReadFileTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
val context = LocalContext.current
viewModel.loadData(context)
ScreenContent(viewModel.uiState.value)
}
}
}
}
#Composable
fun ScreenContent(uiState: UiState) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(text = "Read Data File")
Spacer(modifier = Modifier.height(8.dp))
when {
uiState.isLoading -> CircularProgressIndicator()
uiState.isOnError -> Text(text = "Error when try load data from txt file")
else -> Text(text = "${uiState.fileMessage}")
}
}
}
}
MainViewModel.kt file
If you are unfamiliar with the ViewModel class I recommend this official documentation link.
Here we will focus on "our business rule", what we will actually do to get the data. Since we are dealing with an input/output (I/O) operation we will do this in an appropriate scope using viewModelScope.launch(Dispatchers.IO):
package com.example.kotlinreadfile
import android.content.Context
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.IOException
import java.io.InputStream
class MainViewModel : ViewModel() {
private val _uiState = mutableStateOf(
UiState(
isLoading = true,
isOnError = false,
fileMessage = null
)
)
val uiState: State<UiState> = _uiState
fun loadData(context: Context) {
viewModelScope.launch(Dispatchers.IO) {
try {
val inputStream: InputStream = context.assets.open("data.txt")
val size: Int = inputStream.available()
val buffer = ByteArray(size)
inputStream.read(buffer)
val string = String(buffer)
launch(Dispatchers.Main) {
_uiState.value = uiState.value.copy(
isLoading = false,
isOnError = false,
fileMessage = string
)
}
} catch (e: IOException) {
e.printStackTrace()
launch(Dispatchers.Main) {
_uiState.value = uiState.value.copy(
isLoading = false,
isOnError = true,
fileMessage = null
)
}
}
}
}
}

jetpack compose by state refresh, please try
#Preview
#Composable
fun ReadDataFile() {
var dataText by remember {
mutableStateOf("asd")
}
println("Read Data File")
Column {
Text("Read Data File")
Text(dataText)
}
val context = LocalContext.current
LaunchedEffect(true) {
kotlin.runCatching {
val inputStream: InputStream = context.assets.open("data.txt")
val size: Int = inputStream.available()
val buffer = ByteArray(size)
inputStream.read(buffer)
String(buffer)
}.onSuccess {
it.logE()
dataText = it
}.onFailure {
dataText = "error"
}
}
}

Related

Using async func in Compose Web

I should use Ktor client in Compose Web. But, It can't be called in Compose Web due to async/non-async problem.
Environment is template project made by IntelliJ IDEA.
First, I use this:
val client=HttpClient(Js){
install(ContentNegotiation){
json()
}
}
suspend fun shorterCall(url:String):String{
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
suspend fun main() {
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({ style {
height(100.vh)
margin(0.px)
width(100.vw)
} }) {
Input(type = InputType.Url) {
onInput {
val input=it.value.trim()
if(input.startsWith("http"))
i=shorterCall(input)
else
i="NaN"
}
}
Text(i)
}
}
}
Then, I got that error:
Suspend function can be called only within a coroutine body.
So, I tried another one:
import kotlinx.coroutines.*
fun shorterCall(url:String):String{
var ret:String
suspend fun t():String {
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
runBlocking{
ret=t()
}
return ret
}
//main func is same as upper one.
Then, I got that error:
Unresolved reference: runBlocking
+editing body 1: When I use GlobalScope.launch or js("JSCode"), It raise that error:
e: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
e: java.lang.IllegalStateException: FATAL ERROR: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
(a lot of internal errors bellow)
You can use the GlobalScope.launch() method to launch a job for a request in a browser environment:
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import io.ktor.client.*
import io.ktor.client.engine.js.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.renderComposable
fun main() {
val client = HttpClient(Js) {}
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({
style {
height(100.vh)
margin(0.px)
width(100.vw)
}
}) {
Input(type = InputType.Url) {
onInput {
val input = it.value.trim()
if (input.startsWith("http")) {
GlobalScope.launch {
i = client.shorterCall(input)
}
} else {
i = "NaN"
}
}
}
Text(i)
}
}
}
suspend fun HttpClient.shorterCall(url: String): String {
val response = get(url) {
contentType(ContentType.Application.Json)
}
return response.bodyAsText()
}

How to return a variable from inside coroutine scope in Kotlin

I have a jsoup function inside of a coroutine that scrapes information from a website and then gets a list of maps of all of the information it scraped. However, whenever I try to return the list, this is returned instead:
"Function0<java.util.List<java.util.Map<java.lang.String, ? extends java.lang.String>>>"
Here is the code for the function:
suspend fun popularHome(
pg: String
): Any = withContext(Dispatchers.IO) {
val table = mutableListOf<Map<String, String>>()
val doc: Document = Jsoup
.connect("https://www.novelupdates.com/novelslisting/?sort=2&order=1&status=1&pg=$pg")
.userAgent("Mozilla/5.0")
.referrer("http://www.google.com")
.ignoreContentType(true)
.get()
val imageLists = doc.getElementsByClass("search_img_nu")
val lists = doc.getElementsByClass("search_main_box_nu")
for ((list, imageList) in lists.zip(imageLists)) {
val searches: Elements = list.getElementsByClass("search_title")
val imageSearches: Elements = imageList.getElementsByTag("img")
for ((search, imageSearch) in searches.zip(imageSearches)) {
val titles = search.getElementsByTag("a")
val ranks = search.getElementsByClass("genre_rank")
for ((title, rank) in titles.zip(ranks)) {
val link: String = title.attr("href") // Book Link
val titleName: String = title.text() // Book Title
val imageLink: String = imageSearch.attr("src") // Book Image Link
val rankName: String =
rank.text().replace("#", "") // Book Popularity Rank
table.add(
mutableMapOf<String, String>(
"rank" to rankName,
"title" to titleName,
"link" to link,
"image" to imageLink
)
)
}
}
}
Log.i("tag", table.toString())
return#withContext { table }
}
Here is the code for Main Activity:
package com.example.sushi
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope
import com.example.sushi.service.repos.novelupdates.com.NovelUpdatesScraper
import com.example.sushi.ui.styling.SushiTheme
import kotlinx.coroutines.*
class MainActivity : ComponentActivity() {
val scrape = NovelUpdatesScraper()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SushiTheme {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Hey")
}
}
}
lifecycleScope.launch {
Log.i("tag", scrape.popularHome("1").toString())
}
}
}
No matter where I put the return I can't get it to give me anything but an empty list. Yet when I log the list it gives me all of the information that was scraped.
I already tried this solution:
How to return a value inside a coroutine scope kotlin, android
and this solution:
How to return value from coroutine scope
In the code, when you call return#withContext { table } mean it will return () -> List instead of the List instance.
Just call return#withContext table and it should be fixed.
Btw, I believe you should not set the function return type to Any.
If you know exactly what you gonna do and you expect this function will return a List then the return type should be List<Map<String, String>>, there is no reason to set it as Any. The immediate benefits that can be seen is if you set the return type as List<Map<String, String>> and call return#withContext { table }, the Android Studio IDE will show you what went wrong.

Jetpack compose - trying to get sound to play in viewmodel fun

I'm trying to play a sound outside of #Copmosable because of my application structure.
I have a validation routine which is in my viewmodel and based on the result I would like to trigger a sound but I cannot seem to get context working outside of #Composable
I get the following error in the MasterViewModel:
None of the following functions can be called with the arguments supplied.
create(Context!, Uri!) defined in android.media.MediaPlayer
create(Context!, Int) defined in android.media.MediaPlayer
Any pointers would be great thanks!!
package com.example.soundtest
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.example.soundtest.ui.theme.SoundTestTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SoundTestTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
Greeting()
}
}
}
}
}
#Composable
fun Greeting(mastVM: MasterViewModel = MasterViewModel()) {
Text("Play a sound...")
mastVM.playSound()
}
and the MasterViewModel
package com.example.soundtest
import android.media.MediaPlayer
class MasterViewModel {
fun playSound() {
val mp: MediaPlayer = MediaPlayer.create(this, R.raw.correct)
}
}
I have correct.mp3 save under res->raw-correct.mp3
MasterViewModel Error
I got it working!!!
I just include
#Composable
fun Greeting(mastVM: MasterViewModel = MasterViewModel()) {
val context = LocalContext.current
Text("Play a sound...")
mastVM.playSound(context: context)
}
and then in the MasterViewModel like this
fun playSound(context: Context) {
val mp: MediaPlayer = MediaPlayer.create(context, R.raw.correct)
}

kotlin firebase database won't upload

I've got a little project running for an app to keep track on my LP and cd's
thing is... I want to implement a Firebase database to store all the data in. It's not the first time I'm doing such a thing.. it is the first time i'm stuck with it tho.
here's the base code for the "add item" screen. The idea is that I'm able to attach a bit of data to the items, via textviews, spinners, images etc.
the script works flawless till the "upload image" part.. after that it'll run the firebase upload code... but nothing happens. both the succes and failure log's don't give any output.
could anybody help me with this?
package com.example.firebasic
import android.app.Activity
import android.content.Intent
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.storage.FirebaseStorage
import kotlinx.android.synthetic.main.activity_add.*
import java.util.*
class AddAlbum : AppCompatActivity(), AdapterView.OnItemSelectedListener {
private var spinner:Spinner ? = null
private var arrayAdapter:ArrayAdapter<String> ? = null
private var genres = arrayOf(
"Rock",
"Jazz",
"Soul",
"Metal",
"Punk",
"Pop",
"alt",
"classic",
"other",
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add)
spinner = findViewById(R.id.spinner4)
arrayAdapter = ArrayAdapter(applicationContext, R.layout.spinnerbg, genres)
spinner?.adapter = arrayAdapter
spinner?.onItemSelectedListener = this
//photo
Cover_photo.setOnLongClickListener {
Log.d("AddAlbum", "try to show album cover")
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
startActivityForResult(intent, 0)
return#setOnLongClickListener true
}
accept_button.setOnClickListener {
UploadtoFirebase()
}
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
var genre:String = parent?.getItemAtPosition(position) as String
Toast.makeText(applicationContext, "$genre", Toast.LENGTH_SHORT).show()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
//uri kenmerk
var selectedphotouri: Uri? = null
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 0 && resultCode == Activity.RESULT_OK && data != null){
//proceed and check image
Log.d("AddAlbum", "photo was selected")
selectedphotouri = data.data
val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, selectedphotouri)
val bitmapDrawable = BitmapDrawable(bitmap)
Cover_photo.setBackgroundDrawable(bitmapDrawable)
}
}
private fun UploadtoFirebase(){
if (selectedphotouri == null) return
val filename = UUID.randomUUID().toString()
val ref = FirebaseStorage.getInstance().getReference("/images/$filename")
ref.putFile(selectedphotouri!!)
.addOnSuccessListener {
Log.d("AddAlbum", "succesfuly uploaded")
ref.downloadUrl.addOnSuccessListener {
saveDataToDatabase(it.toString())
Log.d("AddAlbum", "File location: $it")
}
}
}
private fun saveDataToDatabase(Albumphoto: String){
//data input
val Artist = ArtistName.text.toString()
val Album = AlbumName.text.toString()
val Photo = Albumphoto
val data = User(Artist, Album, Photo)
//upload code
val uid = FirebaseAuth.getInstance().uid
val ref = FirebaseDatabase.getInstance().getReference("/album/$uid")
ref.child("01").setValue(data)
.addOnSuccessListener {
Log.d("addalbum", "succesfuly saved data")
}
.addOnFailureListener {
Log.d("addalbum", "still won't work")
}
}
}
class User(val Artist: String, val Albumname: String, val Albumphoto: String)

How to load image in Kotlin Compose desktop?

How to load images from the hard disk when using Kotlin compose on the desktop?
other answers are outdated, as per Compose 1.0.0-beta5 you should do the following:
Image(painterResource("image.jpg"))
if you want to load a bitmap only
val bitmap = useResource("image.jpg") { loadImageBitmap(it) }
Only the file name is required (not full path), but make sure that your resources are in src/main/resources/image.jpg
You can get ImageAsset with this function
fun imageFromFile(file: File): ImageAsset {
return org.jetbrains.skia.Image.makeFromEncoded(file.readBytes()).asImageAsset()
}
Full example:
import androidx.compose.desktop.Window
import androidx.compose.foundation.Image
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.ImageAsset
import androidx.compose.ui.graphics.asImageAsset
import java.io.File
fun main() = Window {
val file = File("D:\\images\\my_image.PNG")
val image = remember { imageFromFile(file) }
Image(asset = image)
}
fun imageFromFile(file: File): ImageAsset {
return org.jetbrains.skia.Image.makeFromEncoded(file.readBytes()).asImageAsset()
}
This worked for me.
Image(bitmap = imageFromResource("image.png"),
"image",
)
contentDescription is necessary, but can be anything you'd like. You can also add modifiers such as
val imageModifier = Modifier
.height(240.dp)
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
Image(bitmap = imageFromResource("header.png"),
"image",
imageModifier,
contentScale = ContentScale.Fit
)
Try this one:
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.res.loadImageBitmap
import java.io.File
val file = File(path)
val imageBitmap: ImageBitmap = remember(file) {
loadImageBitmap(file.inputStream())
}
Image(
painter = BitmapPainter(image = imageBitmap),
contentDescription = null
)
Image.asImageBitmap() is deprecated. Use new Image.toComposeImageBitmap(). For now (01.04.2022) it's not deprecated yet:
#Composable
fun CanvasArea2() {
val image = remember { imageFromFile(File("C:/Users/Admin/Desktop/banana.png")) }
Canvas(modifier = Modifier.background(color = Color(0xFFFFFFFF))) {
drawImage(image)
}
}
fun imageFromFile(file: File): ImageBitmap {
return org.jetbrains.skia.Image.makeFromEncoded(file.readBytes()).toComposeImageBitmap()
}
from the docs : Loading images from device storage or network asynchronously
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.loadImageBitmap
import androidx.compose.ui.res.loadSvgPainter
import androidx.compose.ui.res.loadXmlImageVector
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.xml.sax.InputSource
import java.io.File
import java.io.IOException
import java.net.URL
fun main() = singleWindowApplication {
val density = LocalDensity.current
Column {
AsyncImage(
load = { loadImageBitmap(File("sample.png")) },
painterFor = { remember { BitmapPainter(it) } },
contentDescription = "Sample",
modifier = Modifier.width(200.dp)
)
AsyncImage(
load = { loadSvgPainter("https://github.com/JetBrains/compose-jb/raw/master/artwork/idea-logo.svg", density) },
painterFor = { it },
contentDescription = "Idea logo",
contentScale = ContentScale.FillWidth,
modifier = Modifier.width(200.dp)
)
AsyncImage(
load = { loadXmlImageVector(File("compose-logo.xml"), density) },
painterFor = { rememberVectorPainter(it) },
contentDescription = "Compose logo",
contentScale = ContentScale.FillWidth,
modifier = Modifier.width(200.dp)
)
}
}
#Composable
fun <T> AsyncImage(
load: suspend () -> T,
painterFor: #Composable (T) -> Painter,
contentDescription: String,
modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Fit,
) {
val image: T? by produceState<T?>(null) {
value = withContext(Dispatchers.IO) {
try {
load()
} catch (e: IOException) {
// instead of printing to console, you can also write this to log,
// or show some error placeholder
e.printStackTrace()
null
}
}
}
if (image != null) {
Image(
painter = painterFor(image!!),
contentDescription = contentDescription,
contentScale = contentScale,
modifier = modifier
)
}
}
/* Loading from file with java.io API */
fun loadImageBitmap(file: File): ImageBitmap =
file.inputStream().buffered().use(::loadImageBitmap)
fun loadSvgPainter(file: File, density: Density): Painter =
file.inputStream().buffered().use { loadSvgPainter(it, density) }
fun loadXmlImageVector(file: File, density: Density): ImageVector =
file.inputStream().buffered().use { loadXmlImageVector(InputSource(it), density) }
/* Loading from network with java.net API */
fun loadImageBitmap(url: String): ImageBitmap =
URL(url).openStream().buffered().use(::loadImageBitmap)
fun loadSvgPainter(url: String, density: Density): Painter =
URL(url).openStream().buffered().use { loadSvgPainter(it, density) }
fun loadXmlImageVector(url: String, density: Density): ImageVector =
URL(url).openStream().buffered().use { loadXmlImageVector(InputSource(it), density) }
/* Loading from network with Ktor client API (https://ktor.io/docs/client.html). */
/*
suspend fun loadImageBitmap(url: String): ImageBitmap =
urlStream(url).use(::loadImageBitmap)
suspend fun loadSvgPainter(url: String, density: Density): Painter =
urlStream(url).use { loadSvgPainter(it, density) }
suspend fun loadXmlImageVector(url: String, density: Density): ImageVector =
urlStream(url).use { loadXmlImageVector(InputSource(it), density) }
#OptIn(KtorExperimentalAPI::class)
private suspend fun urlStream(url: String) = HttpClient(CIO).use {
ByteArrayInputStream(it.get(url))
}
*/
Using compose-1.1.0, this works for me.
import org.jetbrains.skia.Image
// ...
Image(
contentDescription = "image",
bitmap = Image.Companion.makeFromEncoded(imageBytes).toComposeImageBitmap()
)