
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: December 9, 2024
Kotlin Flows have become an integral part of modern asynchronous programming. They provide a seamless and concise way to handle asynchronous data streams. When working with flows, two commonly used terminal operators are single() and first(). Although these functions may seem interchangeable at first glance, understanding their nuances is crucial for writing efficient and bug-free code.
In this tutorial, we’ll delve into the differences between the single() and first() functions in Kotlin Flows.
Before diving into the specifics of single() and first(), let’s briefly review Kotlin Flows. A Flow is an asynchronous sequence that emits multiple values over time. They handle streams of data in a non-blocking and efficient manner, making them a powerful tool for reactive programming.
Here are some key aspects highlighting the importance of flows:
The single() function returns the first and only item from a Flow. If the flow is empty, this function throws a NoSuchElementException. If the flow has more than one item, this function throws an IllegalArgumentException.
This makes single() suitable when we expect a Flow to emit a single value and any other combinations of values should error:
@Test
fun testSingleValue() = runBlocking {
val multipleValuesFlow = flowOf(42)
val singleValue = multipleValuesFlow.single()
assertEquals(42, singleValue)
}
In this code, our test case verifies that when a flow containing a single value of 42 returns this one value.
As mentioned earlier, single() throws an exception when the Flow contains anything more than a single value:
@Test
fun testExceptionForMultipleValues() = runBlocking {
val multipleValues = flowOf(42, 43, 44)
val exception = assertFailsWith<IllegalArgumentException> {
runBlocking {
multipleValues.single()
}
}
assertEquals("Flow has more than one element", exception.message)
}
In this example, multipleValues contains three items, and attempting to call single() on it will throw an IllegalArgumentException.
Next, we’ll verify that an empty flow throws a NoSuchElementException:
@Test
fun testIllegalArgumentException() = runBlocking {
val emptyFlow = flowOf<Int>()
val exception = assertFailsWith<NoSuchElementException> {
runBlocking {
emptyFlow.single()
}
}
assertEquals("Flow is empty", exception.message)
}
This enforces that single() is only for when we expect our Flow to have one value, and all other cases should error.
Let’s look at the circumstances where we should use single():
On the other hand, the first() function retrieves the first item emitted by a Flow. It does not require that only one item is emitted; instead, it returns the first item emitted and completes the Flow:
@Test
fun testFirstValue() = runBlocking {
val multipleValuesFlow = flowOf(1, 2, 3)
val firstValue = multipleValuesFlow.first()
assertEquals(1, firstValue)
}
In this code, we obtain the first value from a Flow of integers first(), which in this case is one.
We’ll also verify that an empty Flow throws a NoSuchElementException:
@Test
fun testFirstValueFromEmptyFlow() = runBlocking {
val emptyFlow = emptyFlow<Int>()
val exception = assertFailsWith<NoSuchElementException> {
runBlocking {
emptyFlow.first()
}
}
assertEquals("Expected at least one element", exception.message)
}
Let’s take a look at the situations where we should use first():
Flows are a powerful tool for handling asynchronous data streams, allowing developers to choose between the single() and first() terminal functions based on our specific requirements. The distinction lies in how flows with more than one value are handled. The single() function ensures that only one item is emitted, while the first() function retrieves the initial item without limiting the flow’s size. Both functions will error if the flow is empty.