From 76f157ff428cca7c1abce5287ed00e8e10f8fca6 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 21 Apr 2021 17:44:06 +0300 Subject: [PATCH] Introduce Flow.last and Flow.lastOrNull operators (#2662) Fixes #2246 --- .../api/kotlinx-coroutines-core.api | 2 + .../common/src/flow/terminal/Reduce.kt | 25 +++++++++++ .../common/test/flow/terminal/LastTest.kt | 45 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 kotlinx-coroutines-core/common/test/flow/terminal/LastTest.kt diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 16f2f3d9a7..b4de5d19c2 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -965,6 +965,8 @@ public final class kotlinx/coroutines/flow/FlowKt { public static final fun fold (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun forEach (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)V public static final fun getDEFAULT_CONCURRENCY ()I + public static final fun last (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun lastOrNull (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun launchIn (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Job; public static final fun map (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow; public static final fun mapLatest (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow; diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt index a937adcc8a..1794c9f41c 100644 --- a/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt +++ b/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt @@ -144,3 +144,28 @@ public suspend fun Flow.firstOrNull(predicate: suspend (T) -> Boolean): T } return result } + +/** + * The terminal operator that returns the last element emitted by the flow. + * + * Throws [NoSuchElementException] if the flow was empty. + */ +public suspend fun Flow.last(): T { + var result: Any? = NULL + collect { + result = it + } + if (result === NULL) throw NoSuchElementException("Expected at least one element") + return result as T +} + +/** + * The terminal operator that returns the last element emitted by the flow or `null` if the flow was empty. + */ +public suspend fun Flow.lastOrNull(): T? { + var result: T? = null + collect { + result = it + } + return result +} diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/LastTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/LastTest.kt new file mode 100644 index 0000000000..e7699ccc65 --- /dev/null +++ b/kotlinx-coroutines-core/common/test/flow/terminal/LastTest.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.flow + +import kotlinx.coroutines.* +import kotlin.test.* + +class LastTest : TestBase() { + @Test + fun testLast() = runTest { + val flow = flowOf(1, 2, 3) + assertEquals(3, flow.last()) + assertEquals(3, flow.lastOrNull()) + } + + @Test + fun testNulls() = runTest { + val flow = flowOf(1, null) + assertNull(flow.last()) + assertNull(flow.lastOrNull()) + } + + @Test + fun testNullsLastOrNull() = runTest { + val flow = flowOf(null, 1) + assertEquals(1, flow.lastOrNull()) + } + + @Test + fun testEmptyFlow() = runTest { + assertFailsWith { emptyFlow().last() } + assertNull(emptyFlow().lastOrNull()) + } + + @Test + fun testBadClass() = runTest { + val instance = BadClass() + val flow = flowOf(instance) + assertSame(instance, flow.last()) + assertSame(instance, flow.lastOrNull()) + + } +}