'Set Composable value parameter to result of suspend function

I am new to Compose and Kotlin. I have an application using a Room database. In the frontend, there is a Composable containing an Icon Composable. I want the Icon resource to be set depending on the result of a database operation that is executed within a suspend function.

My Composable looks like this:

@Composable
fun MoviePreview(movie : ApiMoviePreview, viewModel: ApiMovieViewModel) {

    Card(
        modifier = ...
    ) {
        Row(
            modifier = ...
        ) {

            IconButton(
                onClick = {
                //...
            }) {
                Icon(
                    imageVector =
                        // This code does not work, as isMovieOnWatchList() is a suspend function and cannot be called directly
                        if (viewModel.isMovieOnWatchlist(movie.id)) {
                            Icons.Outlined.BookmarkAdded
                        } else {
                            Icons.Filled.Add
                        }
                    ,
                    contentDescription = stringResource(id = R.string.addToWatchlist)
                )
            }
        }
    }
}

The function that I need to call is a suspend function, because Room requires its database operations to happen on a seperate thread. The function isMovieOnWatchlist() looks like this:

suspend fun isMovieOnWatchlist(id: Long) {
    return movieRepository.isMovieOnWatchlist(id)
}

What would the appropriate way be to achieve the desired behaviour? I already stumbled across Coroutines, but the problem is that there seems to be no way to just return a value out of the coroutine function.



Solution 1:[1]

A better approach would be to prepare the data so everything you need is in the data/value class rather than performing live lookup per row/item which is not very efficient. I assume you have 2 tables and you'd probably want a LEFT JOIN however all these details are not included.

With Room it even includes implementations that use the Flow api, meaning it will observe the data when information in either table changes and re-runs the original query to provide you with the new changed dataset.

However this is out of scope of your original question but should you want to explore this then here is a good start : https://developer.android.com/codelabs/basic-android-kotlin-training-intro-room-flow#0

To your original question. This is likely achievable with a LaunchedEffect and some observed MutableState<ImageVector?> object within the composable, something like:

@Composable
fun MoviePreview(
    movie: ApiMoviePreview,
    viewModel: ApiMovieViewModel
) {
    var icon by remember { mutableStateOf<ImageVector?>(value = null) } // null or default icon until update by result below

    Card {
        Row {
            IconButton(onClick = {}) {
                icon?.run {
                    Icon(
                        imageVector = this,
                        contentDescription = stringResource(id = R.string.addToWatchlist))
                }
            }
        }
    }

    LaunchedEffect(Unit) {
        // execute suspending function in provided scope closure and update icon state value once complete
        icon = if (viewModel.isMovieOnWatchlist(movie.id)) {
            Icons.Outlined.BookmarkAdded
        } else Icons.Filled.Add
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mark