https://kotlinlang.org logo
Docs
Join the conversationJoin Slack
Channels
100daysofcode
100daysofkotlin
100daysofkotlin-2021
advent-of-code
aem
ai
alexa
algeria
algolialibraries
amsterdam
android
android-architecture
android-databinding
android-studio
androidgithubprojects
androidthings
androidx
androidx-xprocessing
anime
anko
announcements
apollo-kotlin
appintro
arabic
argentina
arkenv
arksemdevteam
armenia
arrow
arrow-contributors
arrow-meta
ass
atlanta
atm17
atrium
austin
australia
austria
awesome-kotlin
ballast
bangladesh
barcelona
bayarea
bazel
beepiz-libraries
belgium
berlin
big-data
books
boston
brazil
brikk
budapest
build
build-tools
bulgaria
bydgoszcz
cambodia
canada
carrat
carrat-dev
carrat-feed
chicago
chile
china
chucker
cincinnati-user-group
cli
clikt
cloudfoundry
cn
cobalt
code-coverage
codeforces
codemash-precompiler
codereview
codingame
codingconventions
coimbatore
collaborations
colombia
colorado
communities
competitive-programming
competitivecoding
compiler
compose
compose-android
compose-desktop
compose-hiring
compose-ios
compose-mp
compose-ui-showcase
compose-wear
compose-web
connect-audit-events
corda
cork
coroutines
couchbase
coursera
croatia
cryptography
cscenter-course-2016
cucumber-bdd
cyprus
czech
dagger
data2viz
databinding
datascience
dckotlin
debugging
decompose
decouple
denmark
deprecated
detekt
detekt-hint
dev-core
dfw
docs-revamped
dokka
domain-driven-design
doodle
dsl
dublin
dutch
eap
eclipse
ecuador
edinburgh
education
effective-kotlin
effectivekotlin
emacs
embedded-kotlin
estatik
event21-community-content
events
exposed
failgood
fb-internal-demo
feed
firebase
flow
fluid-libraries
forkhandles
forum
fosdem
fp-in-kotlin
framework-elide
freenode
french
fritz2
fuchsia
functional
funktionale
gamedev
ge-kotlin
general-advice
georgia
geospatial
german-lang
getting-started
github-workflows-kt
glance
godot-kotlin
google-io
gradle
graphic
graphkool
graphql
graphql-kotlin
graviton-browser
greece
grpc
gsoc
gui
hackathons
hacktoberfest
hamburg
hamkrest
helios
helsinki
hexagon
hibernate
hikari-cp
hire-me
hiring
hongkong
hoplite
http4k
hungary
hyderabad
image-processing
india
indonesia
inkremental
intellij
intellij-plugins
intellij-tricks
internships
introduce-yourself
io
ios
iran
israel
istanbulcoders
italian
jackson-kotlin
jadx
japanese
jasync-sql
java-to-kotlin-refactoring
javadevelopers
javafx
javalin
javascript
jdbi
jhipster-kotlin
jobsworldwide
jpa
jshdq
juul-libraries
jvm-ir-backend-feedback
jxadapter
k2-early-adopters
kaal
kafka
kakao
kalasim
kapt
karachi
karg
karlsruhe
kash_shell
kaskade
kbuild
kdbc
kgen-doc-tools
kgraphql
kinta
klaxon
klock
kloudformation
kmdc
kmm-español
kmongo
knbt
knote
koalaql
koans
kobalt
kobweb
kodein
kodex
kohesive
koin
koin-dev
komapper
kondor-json
kong
kontent
kontributors
korau
korean
korge
korim
korio
korlibs
korte
kotest
kotest-contributors
kotless
kotlick
kotlin-asia
kotlin-beam
kotlin-by-example
kotlin-csv
kotlin-data-storage
kotlin-foundation
kotlin-fuel
kotlin-in-action
kotlin-inject
kotlin-latam
kotlin-logging
kotlin-multiplatform-contest
kotlin-mumbai
kotlin-native
kotlin-pakistan
kotlin-plugin
kotlin-pune
kotlin-roadmap
kotlin-samples
kotlin-sap
kotlin-serbia
kotlin-spark
kotlin-szeged
kotlin-website
kotlinacademy
kotlinbot
kotlinconf
kotlindl
kotlinforbeginners
kotlingforbeginners
kotlinlondon
kotlinmad
kotlinprogrammers
kotlinsu
kotlintest
kotlintest-devs
kotlintlv
kotlinultimatechallenge
kotlinx-datetime
kotlinx-files
kotlinx-html
kotrix
kotson
kovenant
kprompt
kraph
krawler
kroto-plus
ksp
ktcc
ktfmt
ktlint
ktor
ktp
kubed
kug-leads
kug-torino
kvision
kweb
lambdaworld_cadiz
lanark
language-evolution
language-proposals
latvia
leakcanary
leedskotlinusergroup
lets-have-fun
libgdx
libkgd
library-development
linkeddata
lithuania
london
losangeles
lottie
love
lychee
macedonia
machinelearningbawas
madrid
malaysia
mathematics
meetkotlin
memes
meta
metro-detroit
mexico
miami
micronaut
minnesota
minutest
mirror
mockk
moko
moldova
monsterpuzzle
montreal
moonbean
morocco
motionlayout
mpapt
mu
multiplatform
mumbai
munich
mvikotlin
mvrx
myndocs-oauth2-server
naming
navigation-architecture-component
nepal
new-mexico
new-zealand
newname
nigeria
nodejs
norway
npm-publish
nyc
oceania
ohio-kotlin-users
oldenburg
oolong
opensource
orbit-mvi
osgi
otpisani
package-search
pakistan
panamá
pattern-matching
pbandk
pdx
peru
philippines
phoenix
pinoy
pocketgitclient
polish
popkorn
portugal
practical-functional-programming
proguard
prozis-android-backup
pyhsikal
python
python-contributors
quasar
random
re
react
reaktive
realm
realworldkotlin
reductor
reduks
redux
redux-kotlin
refactoring-to-kotlin
reflect
refreshversions
reports
result
rethink
revolver
rhein-main
rocksdb
romania
room
rpi-pico
rsocket
russian
russian_feed
russian-kotlinasfirst
rx
rxjava
san-diego
science
scotland
scrcast
scrimage
script
scripting
seattle
serialization
server
sg-user-group
singapore
skia-wasm-interop-temp
skrape-it
slovak
snake
sofl-user-group
southafrica
spacemacs
spain
spanish
speaking
spek
spin
splitties
spotify-mobius
spring
spring-security
squarelibraries
stackoverflow
stacks
stayhungrystayfoolish
stdlib
stlouis
strife-discord-lib
strikt
students
stuttgart
sudan
swagger-gradle-codegen
swarm
sweden
swing
swiss-user-group
switzerland
talking-kotlin
tallinn
tampa
teamcity
tegal
tempe
tensorflow
terminal
test
testing
testtestest
texas
tgbotapi
thailand
tornadofx
touchlab-tools
training
tricity-kotlin-user-group
trójmiasto
truth
tunisia
turkey
turkiye
twitter-feed
uae
udacityindia
uk
ukrainian
uniflow
unkonf
uruguay
utah
uuid
vancouver
vankotlin
vertx
videos
vienna
vietnam
vim
vkug
vuejs
web-mpp
webassembly
webrtc
wimix_sentry
wwdc
zircon
Powered by Linen
coroutines
  • e

    esdrasdl

    04/29/2022, 8:50 PM
    Hi, I’m having some trouble to test some state changes in my viewModel using MutableSharedFlow. For example, I have this class
    class SampleViewModel : ViewModel() {
        private val interactions = Channel<Any>()
        private val state = MutableSharedFlow<Any>(
            onBufferOverflow = BufferOverflow.DROP_LATEST,
            extraBufferCapacity = 1
        )
    
        init {
            viewModelScope.launch {
                interactions.receiveAsFlow().collect { action ->
                    processAction(action)
                }
            }
        }
    
        fun flow() = state.asSharedFlow()
    
        fun handleActions(action: Any) {
            viewModelScope.launch {
                interactions.send(action)
            }
        }
    
        suspend fun processAction(action: Any) {
            state.emit("Show loading state")
    
           // process something...
    
            state.emit("Show success state")
        }
    }
    j
    • 2
    • 15
  • r

    R

    04/30/2022, 8:23 AM
    kotlin newb here, I have the following function in
    Controller.kt
    when I attempt to call this from
    main()
    I get an IDE error (see below), can anyone help me resolve this?
    fun <T> awsrun(f: (Ec2Client) -> T): List<T> {
            return clients.values.map { f(it) }
        }
    j
    • 2
    • 2
  • m

    Muhammad Talha

    04/30/2022, 6:10 PM
    Hey all, I'm looking for some help to understand blocking calls within a coroutine. I was looking at an example for Ktor which uses a coroutine to do a blocking db call. Initially I was wondering why the db call needs to be in another coroutine. Since the route handler function is already in a coroutine, I thought it didn't need to be. Now I'm thinking it in fact does because there are limited number of potential threads (I know coroutine and threads don't map 1:1) and we don't want to block even this coroutine because it can potentially be reused to process another request. Is my understanding correct? More generally, is it the case that any time I want to do any time consuming operation, that I should create a new coroutine regardless if I'm already in a coroutine? This is because although there may be other threads available to process information at some point there will be none available and everything will be blocked?
    z
    • 2
    • 2
  • p

    Pablo

    05/04/2022, 8:41 AM
    Hello how could I test a function that receives a
    Flow<T>
    as a parameter and then inside has a
    collect { }
    ? Example
    fun doStuff(flow: Flow<String>) {
       scope.launch {
          flow.collect { 
            if(!it in myList) { 
               dataSource.add(it)
            }
          }
       }
    }
    How would I have this verify that
    dataSource.add(it)
    ? Considering this
    myList
    is something I can mock too
    j
    • 2
    • 9
  • a

    André Martins

    05/04/2022, 2:57 PM
    Hello, when using spring boot webflux with coroutines how can I pass context from the handler to a WebFilter? From the filter to the handler I can put it in the ReactorContext and obtain it via
    coroutineContext[ReactorContext.Key]
    although I was expecting that this context would be restored after calling
    chain.filter
    on my WebFilter, meaning that whenever I put something in
    coroutineContext[ReactorContext.Key]?.context
    it would be visible in Reactor context, but it doesn’t seem to be the case. Is there anyway to do this?
    • 1
    • 1
  • r

    Remy Benza

    05/05/2022, 7:27 AM
    If you need to make 4 API calls concurrently, but 1 of the API calls depends on data from another. What would be the best pattern?
    k
    c
    +2
    • 5
    • 28
  • n

    Nino

    05/05/2022, 10:24 AM
    I'm trying to grasp the concepts behind
    yield()
    but I'm lost. I'd expect this code to print
    [1, 2, 3] \n [1, 2, 3, 4] \n End
    but it doesn't... Why ?
    import kotlinx.coroutines.*
    import kotlinx.coroutines.flow.*
    
    fun main() = runBlocking {
        val mutableStateFlow = MutableStateFlow(emptyList<Int>())
    
        val collectJob = launch {
            combine(
                flowOf(listOf(1, 2, 3)),
                mutableStateFlow
            ) { list1, list2 ->
                list1 + list2
            }.collect { list ->
                println(list)
            }
        }
    
        yield()
        mutableStateFlow.value = listOf(4)
        yield()
        collectJob.cancel()
        
        println("End")
    }
    Playground : https://pl.kotl.in/EpSQVK30I PS : with
    delay(100)
    instead of
    yield()
    , it works. With lower values, I get random results.
    n
    j
    • 3
    • 9
  • r

    rrva

    05/05/2022, 7:32 PM
    If I am writing a piece of code for kotlin multiplatform, I tried to improve the following code to get rid of the GlobalScope.launch()..
    class ChannelWorker() {
        private val requestChannel = Channel<FooRequest>()
        private val responseChannel = Channel<FooResult>()
    
        init {
            GlobalScope.launch {
                while(true) {
                    val receive = requestChannel.receive()
                    val resp = fetchStuff(receive.id)
                    responseChannel.send(FooResult(resp))
                }   
            }
        }
    
        private suspend fun fetchStuff(id: String): Int {
            delay(1000)
            return id.toInt()
        }
    }
    version 2:
    class ChannelWorker {
        private val requestChannel = Channel<FooRequest>()
        private val responseChannel = Channel<FooResult>()
    
        init {
            CoroutineScope(EmptyCoroutineContext).launch {
                for (message in requestChannel) {
                    val resp = fetchStuff(message.id)
                    responseChannel.send(FooResult(resp))
                }
            }
        }
    
        private suspend fun fetchStuff(id: String): Int {
            delay(1000)
            return id.toInt()
        }
    }
    still I am creating a coroutine in a init block, is that really a good thing? how will the lifecycle of that coroutine be managed? What happens when an object of ChannelWorker goes out of scope, how would I stop the channel receiver coroutine? Or is it cleaner if ChannelWorker has start() and stop() methods?
    e
    • 2
    • 27
  • n

    Nandu

    05/05/2022, 8:39 PM
    Hi all, I am developing a Android library with one API exposed to encrypt a file. It returns the Flow. On the client app I have a list of files for which I need to encrypt. Can you please let me know how to chain all the calls for the list of files to be encrypted and run in parallel and get all the encrypted content once so that I can do the further processing.
    g
    • 2
    • 8
  • p

    Paul Woitaschek

    05/06/2022, 5:42 PM
    Could someone shine some light on this (unexpected) behavior of runTest? https://github.com/cashapp/turbine/issues/112
    n
    n
    • 3
    • 2
  • l

    Lilly

    05/06/2022, 11:19 PM
    I have 2 questions about
    stateIn
    . 1. Is this a bad approach:
    // presenter/viewmodel:
    
    val myState: StateFlow<MyState> = api.subscribeToFlow()
      .map {..}
      .stateIn(scope = mainScope + Dispatcher.Default, started = SharingStarted.Lazily, initalValue = MyState.Initial)
    
    // somewhere else:
    fun subscribeToFlow(): Flow<Int> {
      println("thread: ${Thread.currentThread()}.") // prints main thread
      ...
      return someFlow()
    }
    I'm asking because the call to
    api.subscribeToFlow()
    runs in main thread and I don't know how to make it switch to a default thread. I could wrap everything in
    withContext()
    in my
    subscribeToFlow()
    function but is there an alternative? This brings me to my 2nd question. 2. What is a use case for the overloaded
    suspend stateIn(scope: CoroutineScope)
    function? It requires to be called from a suspension function while the simple
    stateIn
    function does not.
    j
    • 2
    • 1
  • j

    juliocbcotta

    05/07/2022, 10:18 AM
    Hey, I have been playing with flows and I found an odd behaviour...
    import kotlinx.coroutines.launch
    import kotlinx.coroutines.delay
    import kotlinx.coroutines.Deferred
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.async
    import kotlinx.coroutines.flow.collect
    import kotlinx.coroutines.flow.combine
    import kotlinx.coroutines.flow.flowOf
    import kotlinx.coroutines.flow.flowOn
    import kotlinx.coroutines.runBlocking
    
    suspend fun flowsWithCombine() {
        val numbersFlow = flowOf(1, 2, 3)
        val lettersFlow = flowOf("A")
        combine(numbersFlow, lettersFlow) { number, letter ->
            "$number$letter"
        }.collect {
           // delay(100)
            println(it)   
        }
    }
    fun main() = runBlocking {
        val deferred: Deferred<Unit> = async {
             println("waiting...")
            flowsWithCombine()
        }
        println(deferred.await())
    }
    If I run the code as above, I get
    waiting...
    1A
    2A
    3A
    kotlin.Unit
    if I uncomment the
    delay
    the result changes
    waiting...
    1A
    3A
    kotlin.Unit
    Why is that ?
    n
    • 2
    • 3
  • x

    xxfast

    05/09/2022, 1:22 AM
    is there a difference between these two? apart from the latter being `suspend`ed
    mutableStateFlow.value = newValue
    mutableStateFlow.emit(newValue)
    e
    n
    • 3
    • 3
  • a

    Alexandre Brown

    05/09/2022, 12:01 PM
    Hello, let's say I have a blocking call to make (like a process builder call or ImageIO.createImageReader()), I currently use
    runBlocking(<http://Dispatchers.IO|Dispatchers.IO>)
    instead of
    runBlocking { }
    , but not sure what is the immediate difference. My app is a backend so no main thread but still want to know the difference. Thanks
    s
    • 2
    • 4
  • f

    Florent Dambreville

    05/09/2022, 12:45 PM
    Hello coroutine community 👋 I’ve just updated my kotlin version to
    1.6.20
    and I get a crash when calling a
    suspendCoroutine
    function :
    var topupCountries: Deferred<Set<String>> = buildCountriesAsync()
        private set
    
    private fun buildCountriesAsync() = CoroutineScope(backgroundDispatcher).async(start = CoroutineStart.LAZY) {
        getCountries()
    }
    
    
    
    private suspend fun getCountries(): Set<String> {
        return try {
            topupCountries.getCompleted()
        } catch (exception: IllegalStateException) {
        
            // Crash here
            fetchRestrictedTopupCountries()
        } }
    
    private suspend fun fetchRestrictedTopupCountries(): Set<String> {
        return suspendCoroutine { continuation ->
            val request = ApiTopupRequestFactory.getTopupCountries(
                listener = {
                    val setResult = it.countries.toSet()
                    continuation.resume(setResult)
                },
                errorListener = {
                    continuation.resume(setOf())
                }
            )
            execute(request)
        }
    }
    The error is :
    java.lang.ClassCastException: kotlin.coroutines.intrinsics.CoroutineSingletons cannot be cast to java.util.Set
    Am I doing something wrong here ? Anyone else with this issue ?
    • 1
    • 4
  • w

    William Reed

    05/10/2022, 5:12 PM
    I have a collection of `Node`s which all have a
    StateFlow<NodeState>
    property. I also have a
    NodeManager
    which in turn has a
    MutableList<Node>
    (which is just exposed as a
    List<Node>
    . At my UI level I want to display something based on each `Node`’s state. it starts to get a little weird since I need to observe each nodes state flow as well as account for the `NodeManager`s collection of nodes possibly changing. Any suggestions for how to approach this such that in my UI level I can end up with a
    List<NodeState>
    so I can render views from it? I’ve tried exposing a
    StateFlow<List<Node>>
    from the
    NodeManager
    but then from the UI level I would need to somehow manage the collection of each node / the node’s possibly getting removed or added
    n
    • 2
    • 2
  • c

    Colton Idle

    05/11/2022, 1:23 AM
    override suspend fun requestBooks(): Flow<List<Book>> {
      return callbackFlow {
        val listener =
            FirebaseFirestore.getInstance().collection("books").addSnapshotListener { value, error
              ->
              trySend(value!!.toObjects())
            }
        awaitClose { listener.remove() }
      }
    }
    How do I move the
    trySend(value!!.toObjects())
    to a background thread. My issue is that
    value!!.toObjects()
    is a deserialization operation that can take a long time. Wrapping
    trySend
    in a launch with Dispatchers.IO seems to help my lag issue in my app, but is it really that easy? Or am i shooting myself in the foot.
    launch(<http://Dispatchers.IO|Dispatchers.IO>) { 
      trySend(value!!.toObjects())
    }
    e
    • 2
    • 9
  • p

    Pablo

    05/11/2022, 9:49 AM
    Anyone that have used turbine for testing SharedStateFlow have faces issues when in
    viewModel
    have the call in the
    init{}
    ? Because the way to test in turbine is to create the
    viewModel
    and then get the
    sharedStateFlow
    and do the calls of the
    viewModel
    but what if the code is in
    init
    when creating the
    viewModel
    is already called this
    init
    so I can not test the first state of this
    sharedFlow
    right? Example :
    @Test 
    fun test = runTest { 
      val viewModel = createViewModel() <-- already call the init
      viewModel.stateFlow.test { 
        //What should I call here? If it's in init...
      }
    }
    j
    a
    • 3
    • 3
  • r

    rrva

    05/11/2022, 2:27 PM
    will jdk19 have any impact on coroutines for kotlin/jvm? Would there be any synergy by scheduling coroutines on lightweight threads (one per coroutine)
    k
    a
    • 3
    • 3
  • l

    louiscad

    05/11/2022, 4:37 PM
    Hello folks! I had to map a
    StateFlow
    to another
    StateFlow
    with a synchronous operation (it's quite cheap, it's only one object allocation), and I didn't want to have to link it to a
    CoroutineScope
    this time, so I wrote this
    mapSync
    extension function, which you can criticize, and copy at will. I'm using it in a single-thread context, but I think it'd work just fine in multi-thread so long as the mapping operation is cheap and doesn't cause heavy recomputations on concurrent accesses.
    import kotlinx.coroutines.flow.*
    
    fun <T, R> StateFlow<T>.mapSync(transform: (T) -> R): StateFlow<R> = object : StateFlow<R> {
    
        override val replayCache: List<R> get() = listOf(value)
    
        override suspend fun collect(collector: FlowCollector<R>): Nothing {
            this@mapSync.collect {
                collector.emit(transform(it))
            }
        }
    
        private var lastUpstreamValue = this@mapSync.value
    
        override var value: R = transform(lastUpstreamValue)
            private set
            get() {
                val currentUpstreamValue: T = this@mapSync.value
                if (currentUpstreamValue == lastUpstreamValue) return field
                field = transform(currentUpstreamValue)
                lastUpstreamValue = currentUpstreamValue
                return field
            }
    }
    I hope it's helpful to some of you!
    :nice: 4
    r
    r
    • 3
    • 3
  • a

    allan.conda

    05/11/2022, 5:38 PM
    I’m trying to migrate my `runBlockingTest`s into
    runTest
    following this migration guide. I’m struggling to migrate my test code which expects the dispatcher to be unpaused throughout the whole test. Is there a way to do this or do I really have to call
    runCurrent()
    for every step I want to assert?
    n
    s
    • 3
    • 6
  • m

    Muhammad Talha

    05/12/2022, 1:00 AM
    When using withContext, as I understand it, it does not create a new coroutine. Does that mean that I need to launch a coroutine myself within it like so?
    suspend fun getData(): Data = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
        val a = async(<http://Dispatchers.IO|Dispatchers.IO>) {
            delay(1000)
            Data("some data...")
        }
        a.await()
    }
    s
    a
    • 3
    • 13
  • t

    therealbluepandabear

    05/13/2022, 8:19 AM
    var toReturn: PixelArt? = null
    
        CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>).launch {
            AppData.pixelArtDB.pixelArtCreationsDao().getAllPixelArtCreations().observe(this@extendedGetCurrentPixelArtObj.findViewTreeLifecycleOwner()!!) {
                toReturn = it[currentIndex]
            }
        }
    
        return toReturn!!
    b
    m
    a
    • 4
    • 5
  • t

    therealbluepandabear

    05/13/2022, 8:19 AM
    how can i use
    .await()
    in this context
    :thread-please: 1
    s
    • 2
    • 2
  • a

    azabost

    05/13/2022, 12:12 PM
    What is the preferred way of unit testing classes which internally launch a never-ending coroutine such as
    SharedFlow
    collection? Let’s say:
    class MyClass(coroutineScope: CoroutineScope, flowToCollect: Flow<Int>) {
        
        var lastObservedResult: Int? = null
    
        init {
            coroutineScope.launch {
                flowToCollect.collect { lastObservedResult = it }
            }
        }
    }
    If I use
    runTest
    and pass the created
    TestScope
    then the test is going to fail after some time because there is a running coroutine.
    @Test
    fun testMyClass() = runTest {
        MyClass(this, flow)
        // do something here, make some assertions etc.
        // at the end, the test is going to fail because of the running coroutine
    }
    After waiting for 60000 ms, the test coroutine is not completing, there were active child jobs (...)
    So should I create another scope instead? Like this, for example?
    val dispatcher = UnconfinedTestDispatcher()
    
    @Test
    fun testMyClass() = runTest(dispatcher) {
        val additionalScope = CoroutineScope(dispatcher)
        MyClass(additionalScope, flow)
        // do something here, make some assertions etc.
    
        additionalScope.close() // Is this necessary, btw? Is the launched coroutine going to leak after the test is finished or something?
        // now the test won't fail, but I must remember to close the additional scope manually
    }
    ➕ 1
    d
    m
    • 3
    • 5
  • a

    azabost

    05/13/2022, 7:48 PM
    With the older coroutines API, I used to use the following utility when I was running some unit tests involving coroutines:
    fun asyncTest(
        context: CoroutineContext = EmptyCoroutineContext, // in practice, it was never changed
        timeoutInMillis: Long = 15000,
        block: suspend CoroutineScope.() -> Unit
    ) {
        runBlocking(context) {
            withTimeout(timeoutInMillis, block)
        }
    }
    It was supposed to fail the test if it took unexpectedly long to execute (real time, not virtual time) to avoid blocking the build agent in the CI system. Now, after the migration to coroutines 1.6.x, I wanted to additionally use the new
    runTest
    function that additionally checks if there are no active coroutines when the test is finished. Therefore I’m wondering if something like this is a good enough replacement for my previous utility:
    fun asyncTest(
        context: CoroutineContext = UnconfinedTestDispatcher(),
        timeoutInMillis: Long = 15000,
        block: suspend TestScope.() -> Unit
    ) = runBlocking {
        withTimeout(timeoutInMillis) {
            runTest(context, timeoutInMillis, block)
        }
    }
    // EDIT: Nope, it doesn’t work the way I expected. It doesn’t work in the same way as my previous utility, e.g. when there is a coroutine with an endless while-loop with a delay inside it. Any ideas?
    j
    • 2
    • 8
  • a

    azabost

    05/14/2022, 12:49 AM
    Hey. I’m trying to understand the
    runTest
    behavior that makes it run all the delays at the end of the test body. I think I saw it mentioned somewhere, maybe in some GitHub issue, but I can’t find any mentions in the documentation regarding that behavior. Example:
    private val testCoroutineScheduler = TestCoroutineScheduler()
    private val standardTestDispatcher = StandardTestDispatcher(testCoroutineScheduler)
    
    @Test
    fun `should execute the coroutine with delays`() = runTest(standardTestDispatcher) {
        var iterations = 0
    
        val job = launch {
            while(isActive) {
                println("iteration")
                iterations++
                delay(10)
            }
        }
    
        iterations.shouldEqual(0) // this is an assertion
    
        runCurrent()
        iterations.shouldEqual(1)
    
        advanceTimeBy(5)
        iterations.shouldEqual(1)
        advanceTimeBy(5)
        iterations.shouldEqual(1)
    
        runCurrent()
        iterations.shouldEqual(2)
    
        advanceTimeBy(10)
        runCurrent()
        iterations.shouldEqual(3)
    
        job.cancel()
    }
    If I remove
    job.cancel()
    at the end, the test is never going to finish and I’m going to be flooded by the
    println
    invocations. That behavior is quite problematic for me sometimes and that’s why I’m trying to understand it better. There are some cases where I: • want to control the scheduler to see how many times something periodical happened • want the test to finish despite the lack of direct access for the launched coroutine containing the delay For example:
    private val testCoroutineScheduler = TestCoroutineScheduler()
    private val standardTestDispatcher = StandardTestDispatcher(testCoroutineScheduler)
    
    class Refresher(scope: CoroutineScope, refreshingDispatcher: CoroutineDispatcher) {
    
        var refreshCount = 0
    
        init {
            scope.launch(refreshingDispatcher) {
                while (isActive) {
                    refreshCount++
                    delay(10)
                }
            }
        }
    }
    
    @Test
    fun `refreshing should work`() = runTest(standardTestDispatcher) {
        val refresher = Refresher(this, standardTestDispatcher)
    
        refresher.refreshCount.shouldEqual(0)
        runCurrent()
        refresher.refreshCount.shouldEqual(1)
        advanceTimeBy(15)
        refresher.refreshCount.shouldEqual(2)
    
        // this test never ends and I can't cancel the launched coroutine
    }
    I can make it work by removing
    runTest
    but I’m simply not sure if this is what I should do in this scenario.
    @Test
    fun `refreshing should work`() {
        val refresher = Refresher(CoroutineScope(standardTestDispatcher), standardTestDispatcher)
    
        refresher.refreshCount.shouldEqual(0)
        testCoroutineScheduler.runCurrent()
        refresher.refreshCount.shouldEqual(1)
        testCoroutineScheduler.advanceTimeBy(15)
        refresher.refreshCount.shouldEqual(2)
    }
    Any advices?
    j
    • 2
    • 3
  • j

    juliocbcotta

    05/14/2022, 8:43 PM
    So, I am trying to write some tests for Flows using Turbine lib and I found myself in the need to assert some flow emissions that would be triggered after I call
    .test
    ... something like
    val flow = MutableStateFlow(1)
        flow.test {
            assertEquals(awaitItem(), 1)
            assertEquals(awaitItem(), 2) // <-- waits forever
    }
    flow.emit(2)
    Would anyone have a tip on how to properly test it ?
    p
    j
    j
    • 4
    • 9
  • k

    K Merle

    05/15/2022, 8:51 AM
    I need builder like
    callbackFlow
    but I do not need
    awaitClose()
    callback. What would be a good candidate for it in flow?
    c
    o
    +3
    • 6
    • 10
  • r

    Robert Williams

    05/15/2022, 11:04 AM
    I noticed an interesting coroutine issue when updating our app kotlin version from 1.4 to 1.6
    d
    • 2
    • 11
Powered by Linen
Title
r

Robert Williams

05/15/2022, 11:04 AM
I noticed an interesting coroutine issue when updating our app kotlin version from 1.4 to 1.6
Here's a reproduction case: https://play.kotlinlang.org/#eyJ2ZXJzaW9uIjoiMS42LjIxIiwicGxhdGZvcm0iOiJqYXZhIiwiYXJncy[…]JhcHBlcihtYXBGdW5jdGlvbihpdCkpfSwgV3JhcHBlcihudWxsKSkifQ==
On 1.4 there's an error but we do get the correct output
Wrapper(value=3)
On 1.6 it just fails with a different error
There's also a slightly less functional version
map2
which works correctly on all versions
And if you take out the
delay
it obviously works because it's not suspending
Not sure if this issue is in coroutines, compiler or it's just the code is wrong but maybe someone here knows more
d

Dan Fingal-Surma

05/16/2022, 7:19 AM
Doing some exploring. I started inlining code and this succeeds:
inline fun <OldT, NewT> Wrapper<OldT>.map(mapFunction : (OldT) -> NewT) : Wrapper<NewT> = 
    value?.let { wrap(mapFunction(it)) } ?: Wrapper(null)
But this fails:
inline fun <OldT, NewT> Wrapper<OldT>.map(mapFunction : (OldT) -> NewT) : Wrapper<NewT> = 
    value?.let(mapFunction.andThen(::wrap)) ?: Wrapper(null)
If I try to change
andThen
to not be an extension function, I get a compiler error:
inline fun <OldT, NewT> Wrapper<OldT>.map(crossinline mapFunction : (OldT) -> NewT) : Wrapper<NewT> =
    value?.let(andThen(mapFunction, ::wrap)) ?: Wrapper(null)

inline fun <A, B, C> andThen(crossinline g: ((A) -> B), crossinline f: (B) -> C): (A) -> C {
    return { it: A -> f(g(it)) }
}
Suspension functions can be called only within coroutine body
I get the same error if I manually inline the map function:
wrapped.value?.let(andThen({ oldVal ->
            delay(1000)
            oldVal.toString()
        }, ::wrap)) ?: Wrapper(null)
If I take your original code and annotate everything with
suspend
everywhere I can, it works: https://pl.kotl.in/zrgS-jyXi So it seems like something is getting mixed up in the loose way that inline allow accepting suspend funs. Note that the error is on the
T
from
Wrapper
, which indicates to me that the argument
it
to
mapperFun
in
mapOrDefault
is getting put into the wrong context somehow. It seems like a bug.
r

Robert Williams

05/16/2022, 9:22 AM
Thanks Dan, the other interesting thing I found but forgot to add was this bug: https://youtrack.jetbrains.com/issue/KT-44567/IncompatibleClassChangeError-caused-by-coroutines-and-Vertx
Which looks almost identical but was apparently fixed in kotlin 1.5 🤷
d

Dan Fingal-Surma

05/17/2022, 12:57 AM
Probably worth filing a bug then
r

Robert Williams

05/17/2022, 8:55 AM
Done: https://github.com/Kotlin/kotlinx.coroutines/issues/3288 🐛
View count: 4