// KLUG - Kotlin Flows
Using Flows - Builders
val flow1 = flowOf(1, 2, 3) // Observable.fromArray
val flow2 = listOf(1, 2, 3).asFlow() // toObservable()
val empty = emptyFlow() // Observable.empty()
Slide 16
Slide 16 text
// KLUG - Kotlin Flows
Using Flows - Builders
val list = flowOf(1, 2, 3).toList() // Observable.toList()
val set = flowOf(1, 2, 3).toSet()
val linkedList = flowOf(1, 2, 3).toCollection(LinkedList())
// KLUG - Kotlin Flows
Flow Internals - Operator fusing
### Operator fusion
Adjacent applications of [channelFlow], [flowOn], [buffer], [produceIn], and
[broadcastIn] are always fused so that only one properly configured channel is
used for execution.
1. flowOf(1, 2, 3)
2. .flowOn(Dispatchers.IO)
3. .buffer(20)
4. .flowOn(Dispatchers.Main)
5. .count { }
< channel-based
< channel-based
< channel-based
all reusing single channel
// KLUG - Kotlin Flows
Flow Internals - Backpressure
1. val flow = flow {
2. for (value in 0..5) {
3. print("out $value")
4. emit(value)
5. }
6. }
7. flow.collect {
8. print("in $it")
9. delay(100)
10. }
-> out 0, in 0, out 1, in 1, out 2, in 2, ...
Slide 54
Slide 54 text
// KLUG - Kotlin Flows
Flow Internals - Backpressure
1. val flow = flow {
2. for (value in 0..5) {
3. print("out $value")
4. emit(value)
5. }
6. }
7. flow.buffer(1).collect {
8. delay(100)
9. print("in $it")
10. }
-> out 0, out 1, out 2, in 0, out 3, in 1, out 4, in 2, out 5, ...
Slide 55
Slide 55 text
// KLUG - Kotlin Flows
Flow Internals - Backpressure
1. val flow = flow {
2. for (value in 0..5) {
3. print("out $value")
4. emit(value)
5. }
6. }
7. flow.buffer(Channel.CONFLATED).collect {
8. delay(100)
9. print("in $it")
10. }
-> out 0, out 1, out 2, out 3, out 4, out 5, in 0, in 5
Slide 56
Slide 56 text
// KLUG - Kotlin Flows
Flow Internals - Cold vs Hot
// KLUG - Kotlin Flows
Flow Internals - Cold vs Hot
1. val channel = flow {
2. print("emitting")
3. emit(1)
4. emit(2)
5. }.produceIn(scope)
6.
7. delay(100)
8.
9. print("collecting")
10. for (value in channel) {
11. print(value)
12. }
-> emitting, collecting, 1, 2
HOT
Convert to channel
Slide 59
Slide 59 text
// KLUG - Kotlin Flows
Extending Flows
When it is just not enough.
Slide 60
Slide 60 text
// KLUG - Kotlin Flows
Extending Flows - take() example
1. public fun Flow.take(count: Int) = flow {
2. var consumed = 0
3. try {
4. collect { value ->
5. emit(value)
6. if (++consumed == count) {
7. throw AbortFlowException()
8. }
9. }
10. } catch (e: AbortFlowException) {
11. // Nothing, bail out
12. }
13. }
Slide 61
Slide 61 text
// KLUG - Kotlin Flows
Extending Flows - AndroidX Room
@Dao
interface UsersDao {
@Query("SELECT * FROM users WHERE id == :id")
suspend fun getById(id: String): UserRecord
@Query("SELECT * FROM users WHERE id == :id")
fun rxTrackById(id: String): Flowable
@Query("SELECT * FROM users WHERE id == :id")
fun flowTrackById(id: String): Flow
}
// KLUG - Kotlin Flows
Benchmarks - Why Job.cancel is faster?
internal class JobCancellationException constructor(...)
: CancellationException(...), CopyableThrowable {
// ...
override fun fillInStackTrace(): Throwable {
if (DEBUG) {
return super.fillInStackTrace()
}
/*
* In non-debug mode we don't want to have a stacktrace
* on every cancellation, parent job reference is enough.
* Stacktrace of JCE is not needed most of the time and hurts performance.
*/
return this
}
// ...
}
// KLUG - Kotlin Flows
Conclusion
Just some thoughts about all of this greatness
Slide 74
Slide 74 text
// KLUG - Kotlin Flows
Conclusion
Pros:
● Built-in suspend functions support
● No need to handle cancellation
● Easy to write simple extensions
Cons:
● Much bigger overhead -> much slower (especially for cancellation)
● Requires scope to call terminal function
● More edge-cases (for ex. context preservation, exception transparency)
● Most interesting functions are marked as FlowPreview/ExperimentalCoroutinesApi
Slide 75
Slide 75 text
More info:
1. Always read sources, a lot of interesting stuff there :)
2. https://kotlinlang.org/docs/reference/coroutines/flow.html