Wie rufe ich Kotlin-Coroutine in zusammensetzbaren Funktionsrückrufen auf?

Lesezeit: 3 Minuten

Nyctas Benutzeravatar
Nycta

Ich möchte eine Suspend-Funktion innerhalb eines Callbacks einer Composable-Funktion aufrufen.

suspend fun getLocation(): Location? { /* ... */ }

@Composable
fun F() {

    val (location, setLocation) = remember { mutableStateOf<Location?>(null) }

    val getLocationOnClick: () -> Unit = {
        /* setLocation __MAGIC__ getLocation */
    }

    Button(onClick = getLocationOnClick) {
        Text("detectLocation")
    }

}

Wenn ich Rx verwendet hätte, dann könnte ich es einfach tun subscribe.

ich könnte invokeOnCompletion und dann getCompletedaber diese API ist experimentell.

Ich kann es nicht verwenden launchInComposition In getLocationOnClick Weil launchInComposition Ist @Composable Und getLocationOnClick kann nicht sein @Composable.

Was wäre der beste Weg, um das Ergebnis einer Suspendierungsfunktion innerhalb einer regulären Funktion innerhalb zu erhalten? @Composable Funktion?

  • Sie können eine Funktion eines ViewModel aufrufen, die eine Suspend-Funktion für den ViewmodelScope startet

    – 2. Jan. 222

    29. September 2020 um 9:26 Uhr

  • Andernfalls können Sie, wenn Sie einen Verweis auf eine AppCompatActivity haben, deren Lebenszyklusbereich verwenden

    – 2. Jan. 222

    29. September 2020 um 9:27 Uhr

  • Du meinst wie SomeScope.async { setLocation(getLocation()) }? Vielen Dank, das funktioniert tatsächlich, ich hätte nicht erwartet, dass es so einfach ist. Könnten Sie das bitte als Antwort kommentieren (damit ich diese Frage als gelöst markieren kann)?

    – Nycta

    30. September 2020 um 7:27 Uhr

Erstellen Sie einen Coroutinen-Bereich, der an den Lebenszyklus Ihres Composable gebunden ist, und verwenden Sie diesen Bereich, um Ihre Suspendierungsfunktion aufzurufen

suspend fun getLocation(): Location? { /* ... */ }

@Composable
fun F() {
    // Returns a scope that's cancelled when F is removed from composition
    val coroutineScope = rememberCoroutineScope()

    val (location, setLocation) = remember { mutableStateOf<Location?>(null) }

    val getLocationOnClick: () -> Unit = {
        coroutineScope.launch {
            val location = getLocation()
        }
    }

    Button(onClick = getLocationOnClick) {
        Text("detectLocation")
    }
}

Benutzer-Avatar von Dirk Hoffmann
Dirk Hoffmann

Das funktioniert bei mir:

@Composable
fun TheComposable() {

    val coroutineScope = rememberCoroutineScope()
    val (loadResult, setLoadResult) = remember { mutableStateOf<String?>(null) }

    IconButton(
        onClick = {
            someState.startProgress("Draft Loading...")
            coroutineScope.launch {
                withContext(Dispatchers.IO) {
                    try {
                        loadResult = DataAPI.getData() // <-- non-suspend blocking method
                    } catch (e: Exception) {
                        // handle exception
                    } finally {
                        someState.endProgress()
                    }
                }
            }

        }
    ) {
        Icon(Icons.TwoTone.Call, contentDescription = "Load")
    }

Ich habe auch die folgende Hilfsfunktion ausprobiert, um Entwicklerkollegen zu zwingen, Ausnahmen zu behandeln und schließlich den Status zu bereinigen (auch um den gleichen Code (vielleicht!?) etwas kürzer und (vielleicht!?) etwas lesbarer zu machen):

fun launchHelper(coroutineScope: CoroutineScope,
                 catchBlock: (Exception) -> Unit,
                 finallyBlock: () -> Unit,
                 context: CoroutineContext = EmptyCoroutineContext,
                 start: CoroutineStart = CoroutineStart.DEFAULT,
                 block: suspend CoroutineScope.() -> Unit
): Job {
    return coroutineScope.launch(context, start) {
        withContext(Dispatchers.IO) {
            try {
                block()
            } catch (e: Exception) {
                catchBlock(e)
            } finally {
                finallyBlock()
            }
        }
    }
}

und so verwenden Sie diese Hilfsmethode:

@Composable
fun TheComposable() {

    val coroutineScope = rememberCoroutineScope()
    val (loadResult, setLoadResult) = remember { mutableStateOf<String?>(null) }

    IconButton(
        onClick = {
            someState.startProgress("Draft Loading...")
            launchHelper(coroutineScope,
                catchBlock = { e -> myExceptionHandling(e) },
                finallyBlock = { someState.endProgress() }
            ) {
                loadResult = DataAPI.getData() // <-- non-suspend blocking method
            }

        }
    ) {
        Icon(Icons.TwoTone.Call, contentDescription = "Load")
    }

}

Sie können den viewModelScope eines ViewModel oder einen anderen Coroutine-Bereich verwenden.

Beispiel für die Löschaktion für ein Element aus LazyColumnFor, die einen Suspend-Aufruf erfordert, der von einem ViewModel verarbeitet wird.

     class ItemsViewModel : ViewModel() {

        private val _itemList = MutableLiveData<List<Any>>()
        val itemList: LiveData<List<Any>>
            get() = _itemList

        fun deleteItem(item: Any) {
            viewModelScope.launch(Dispatchers.IO) {
                TODO("Fill Coroutine Scope with your suspend call")       
            }
        }
    }

    @Composable
    fun Example() {
        val itemsVM: ItemsViewModel = viewModel()
        val list: State<List<Any>?> = itemsVM.itemList.observeAsState()
        list.value.let { it: List<Any>? ->
            if (it != null) {
                LazyColumnFor(items = it) { item: Any ->
                    ListItem(
                        item = item,
                        onDeleteSelf = {
                            itemsVM.deleteItem(item)
                        }
                    )
                }
            } // else EmptyDialog()
        }
    }

    @Composable
    private fun ListItem(item: Any, onDeleteSelf: () -> Unit) {
        Row {
            Text(item.toString())
            IconButton(
                onClick = onDeleteSelf,
                icon = { Icons.Filled.Delete }
            )
        }
    }

  • habe einen kurzen Blick darauf geworfen Quellcode der viewModelScope-Erweiterung. Könnte es ohne ViewModel nur mit verwendet werden? val scope = remember { CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) } und dann onActive { onDispose { scope.cancel() } }?

    – Nycta

    1. Okt. 2020 um 5:33

  • Nicht großartig, man weiß nicht wirklich, wann die Operation endet. Verwenden Sie einfach „rememberCoroutinesScope()“ und verwandeln Sie „deleteItem()“ in eine Suspendierungsfunktion

    – Hey Hey Hey

    14. November 2020 um 16:18 Uhr

  • Ich stimme @heyheyhey zu, wir wissen nicht, wann das Composable aufgerufen wird, es wird möglicherweise beliebig oft abgerufen, daher ist es besser, es zu verwenden rememberCoroutineScope über viewModelScope

    – GTXtreme

    27. Mai 2022 um 4:34

1452680cookie-checkWie rufe ich Kotlin-Coroutine in zusammensetzbaren Funktionsrückrufen auf?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy