Home > database >  Wait for result from Coroutine and then use it in Composable function
Wait for result from Coroutine and then use it in Composable function

Time:10-05

I've been going insane trying to figure this out, and I am just unsure what to do anymore.

I am creating a video scraper, and it has the following function which scrapes the video source from a URL that has been given as the parameter:

fun scrapeVideoSrcFromUrl(url: String): String? {
    val document = Jsoup.connect(url).get()

    for (element in document.getElementsByTag("script")) {
        if (element.attr("type") == "application/ld json") {
            val content = element.data()
            val array = JsonParser.parseString(content).asJsonArray

            val embedUrl = Gson().fromJson(array.get(0).asJsonObject.get("embedUrl"), String::class.java)
            var embedId = ""

            for (char in embedUrl.dropLast(1).reversed()) {
                if (char != '/') {
                    embedId  = char
                } else {
                    break
                }
            }

            val doc = Jsoup.connect("$RUMBLE_API_URL${embedId.reversed()}").ignoreContentType(true).get()
            val jsonData = doc.getElementsByTag("body").first()?.text()

            val mp4 = JsonParser.parseString(jsonData).asJsonObject.get("u").asJsonObject.get("mp4").asJsonObject.get("url").toString()

            return mp4.replace("\"", "")
        }
    }

    return null
}

I want to show this in a dialog for a certain link using ExoPlayer, so I did the following:

@Composable
fun VideoPlayer(videoSrc: String) {
    val context = LocalContext.current

    val exoPlayer = remember {
        ExoPlayer.Builder(context).build().apply {
            setMediaItem(
                MediaItem.fromUri(
                    videoSrc
                )
            )
            prepare()
            playWhenReady = true
        }
    }

    Box(modifier = Modifier.fillMaxSize()) {
        DisposableEffect(key1 = Unit) {
            onDispose {
                exoPlayer.release()
            }
        }

        AndroidView(
            factory = {
                StyledPlayerView(context).apply {
                    player = exoPlayer
                    layoutParams =
                        FrameLayout.LayoutParams(
                            ViewGroup.LayoutParams.MATCH_PARENT,
                            ViewGroup.LayoutParams.MATCH_PARENT
                        )
                }
            }
        )
    }
}

Then, in the main Composable:

if (openDialog) {
        AlertDialog(
            onDismissRequest = {
                openDialog = false
            },
            title = {
                Column {
                    Text(
                        text = viewModel.currentRumbleSearchResult?.title ?: ""
                    )
                    Spacer(
                        Modifier.height(8.dp)
                    )
                    Text(
                        text = "By ${viewModel.currentRumbleSearchResult?.channel?.name ?: ""}",
                        style = MaterialTheme.typography.titleSmall
                    )
                }
            },
            text = {
                        VideoPlayer(RumbleScraper.create().scrapeVideoSrcFromUrl("https://rumble.com/v1m9oki-our-first-automatic-afk-farms-locals-minecraft-server-smp-ep3-live-stream.html")!!)
            
            },
            confirmButton = {
                TextButton(
                    onClick = {
                        openDialog = false
                    }
                ) {
                    Text("Exit")
                }
            }
        )
    }

After running that code I keep getting NetworkOnMainThread exceptions, and I tried many things to fix it but nothing worked.

So I am unsure what to do as to how I can go around fixing this. I was wondering how I would go around waiting in the background for a result and then show it in the Compose function when it returns the value? I am completely stumped, any help would be appreciated.

CodePudding user response:

You can do something like this:

var videoSrc by remember { mutableStateOf<String?>(null) }

LaunchedEffect(Unit) {
    withContext(Dispatchers.IO) {
        videoSrc = RumbleScraper.create().scrapeVideoSrcFromUrl("")
    }
}

text = { VideoPlayer(videoSrc) }

You can also call the scrapeVideoSrcFromUrl inside your viewModel and update some state that you will use in UI.


If you want to run it in response to some event like item click, you will be better of with something like this:

val scope = rememberCoroutineScope()

Button(
    onClick = {
        scope.launch {
            withContext(Dispatchers.IO) { ... }
        }
    }
)
  • Related