https://kotlinlang.org logo
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

    Exerosis

    02/24/2022, 3:20 PM
    What kind of overhead is involved in marking functions as suspending when you don't have any suspension points? For example if I have a special data structure called Mutable<Key, Value> and it has get(Key) and set(Key, Value) right now those are not suspending functions... but sometimes I need to make a data structure that wraps a database or does some other costly operation. If I changed the whole definition to suspend fun get(Key) etc. I would pretty much need to mark every function in my whole framework suspend. Other than the fact that from a readability/usability standpoint that sucks, how much would I be crippling myself by doing that? Would it make more sense to just wait on java's project loom and use that instead/along side kotlin coroutines?
    c
    e
    • 3
    • 4
  • s

    Stephen Edwards

    02/24/2022, 3:42 PM
    The kdoc for
    runBlocking
    claims it blocks the thread from which it is called (interuptibly). But I have observed behaviour where other coroutines waiting for dispatch on that thread (e.g. on the main thread) will be dispatched by the event loop because
    runBlocking
    falls back to using the calling thread's
    eventLoop
    even when a specific other dispatcher (which may be busy) is specified for that which to run the coroutine launched by
    runBlocking
    on. Why is this and is this a bug? If not what is the explanation for this choice of behaviour? Code reproduction example in thread:
    e
    s
    +2
    • 5
    • 55
  • a

    allan.conda

    02/24/2022, 7:23 PM
    I have a source flow that I want to map,filter in different ways. Would I need to create multiple flows and launch&collect them from separate coroutines, or is that a bad thing and there is a better way to chain it within a single coroutine? The coroutine is dispatched to Main, so I'm concerned on launching many separate coroutines on it (though not sure the cost of doing so)
    s
    • 2
    • 7
  • d

    David W

    02/25/2022, 3:26 AM
    What's the simplest way to transform a
    StateFlow
    into a different one? This is what I've done, not sure if it's the best or even working...
    val newStateFlow = originalStateFlow
            .map { it.doTransform() }
            .stateIn(scope = CoroutineScope(Job()), started = SharingStarted.Eagerly, initialValue = false)
    j
    • 2
    • 4
  • a

    antrax

    02/25/2022, 4:52 PM
    Do you guys enable stacktrace recovery (https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/topics/debugging.md#stacktrace-recovery) in your production builds? I wonder why it's disabled by default. It's quite hard to debug crashes in production having only the last part of the stacktrace reported.
    :yes: 1
    👀 4
    e
    • 2
    • 3
  • t

    theapache64

    02/28/2022, 7:49 AM
    ✅ RESOLVED
    CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>).launch {
        launch {
            println("A")
            delay(5000)
            println("B")
        }
        launch(context = Job()) {
            delay(1000)
            throw IOException()
        }
    }
    The above snippet will crash the android app (obviously), but the app prints
    B
    5 seconds after the crash. Note that removing
    Job()
    gives expected result. Any idea why? 🤔
    s
    • 2
    • 3
  • t

    theapache64

    02/28/2022, 8:12 AM
    ✅ RESOLVED Do we really need
    job.join()
    here? With
    delay(1300)
    we’re already waiting, right? 🤔
    g
    • 2
    • 2
  • m

    Marco Righini

    02/28/2022, 4:06 PM
    We have this code in the codebase
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            viewModel.uiEvent
                .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
                .onEach { event ->
                    when (event) {
                        // code there
                    }
                }.launchIn(lifecycleScope)
    }
    and we have a problem when we add the fragment to the back stack: we end up having coroutines when we press back and go back to this fragment (onViewCreated is called again) The right approach seems to be to change
    lifecycleScope
    with
    viewLifecycleOwner.lifecycleScope
    . Is it ok? If yes, could be worth to have a lint rule? Thanks!
    t
    i
    • 3
    • 4
  • e

    eygraber

    02/28/2022, 9:36 PM
    What happens if I try to
    launch
    on a
    CoroutineScope
    that has been cancelled? Does it throw an exception, or does it just get swallowed?
    s
    • 2
    • 2
  • m

    mzgreen

    03/01/2022, 11:23 AM
    How do I choose between
    CompletableDeferred
    and
    suspendCancellableCoroutine
    when wrapping a callback based API into a coroutines API? Here is an example of an API wrapped using `CompletableDeferred`: https://handstandsam.com/2022/02/28/install-referrer-kotlin-extension/ And here are examples of APIs wrapped using `suspendCancellableCoroutine`: https://chris.banes.dev/suspending-views/ When is the one approach preferable over the other one?
    s
    e
    • 3
    • 9
  • m

    mcpiroman

    03/02/2022, 10:14 AM
    This is a common pattern but I'm still unsure how to go about it: I have a prepared list of items I want to process with parallelization, is this the preferred way:
    suspend fun processAll(items: List<Item>) = coroutineScope {
      val channel = Channel<Item>(Channel.UNLIMITED)
      items.forEach { channel.trySendBlocking(it).getOrThrow() }
    
       repeat(4) {
            launch {
                for (item in channel) // process
            }
        }
    }
    Bonus point - is there a facility to automatically pick an optimal level of parallelism, instead of, in this example, hardcoded 4?
    s
    • 2
    • 8
  • k

    khairil.ushan

    03/02/2022, 7:06 PM
    Hi Everyone, I have a suspending function in my KMP module that looks like this
    @Throws(
            CancellationException::class,
            UnauthorizedError::class
        )
        suspend fun login() {
            runBlocking {
                val fakeWaitingTime = 300L
                delay(fakeWaitingTime)
            }
        }
    Then if I try to execute that login function inside Swift concurrency context like this
    Task {
        try? await Auth().login()
    }
    It will crash with this error:
    Function doesn't have or inherit @Throws annotation and thus exception isn't propagated from Kotlin to Objective-C/Swift as NSError.
    It is considered unexpected and unhandled instead. Program will be terminated.
    Uncaught Kotlin exception: kotlin.native.IncorrectDereferenceException: illegal attempt to access non-shared
    I am using this version of coroutines
    const val coroutines = "1.6.0-native-mt"
    Is there any work around for this? Thank you.
    j
    r
    • 3
    • 13
  • s

    Sourabh Rawat

    03/03/2022, 4:29 AM
    Is this not fixed? https://youtrack.jetbrains.com/issue/KTIJ-18630 Can't access
    reservation
    parameter in debugger at the debug point at line 26. It says
    This variable is inaccessible because it isn't used after the last suspension point
    Using coroutines
    1.6.0
    e
    • 2
    • 5
  • s

    Sourabh Rawat

    03/03/2022, 8:58 AM
    Blockhound's
    allowBlockingCallsInside
    does not work if I don't run the test in debug mode? I am thinking its a stack trace recovery issue during normal tests? I tried following to replicate courotines debug mode during normal test runs
    tasks.withType<Test> {
        useJUnitPlatform()
        jvmArgs = listOf("-XX:+AllowRedefinitionToAddDeleteMethods", "-ea", "-Dkotlinx.coroutines.debug=on", "-Dkotlinx.coroutines.stacktrace.recovery=true")
    }
    but it didnt work Any help is apprecitated!
    d
    • 2
    • 1
  • j

    jean

    03/03/2022, 2:28 PM
    Is there an equivalent of the amb operator with an array of
    Flow
    ?
    j
    e
    • 3
    • 7
  • t

    Tower Guidev2

    03/03/2022, 2:34 PM
    hi i have the following use case and i am sure i have seen a flow implementation that supports it, however now i cannot find it 🤦‍♂️ i wish to execute a number of processes in parallel and consume the first one that completes and automatically cancel all remaining processes does Kotlin flow have such a function? or have i miss remembered and confused flow with LiveData?
    j
    l
    u
    • 4
    • 9
  • t

    tseisel

    03/03/2022, 6:43 PM
    I'm trying to design an efficient, realtime audio processing pipeline using coroutines. Each pipeline stage would be contained in a coroutine, and bytes transmitted using channels. How to send bytes between coroutines ? Both ByteArray and ByteBuffer are not cheap to create and mutable, so those are not safe to share between coroutines.
    s
    e
    • 3
    • 12
  • z

    Zoltan Demant

    03/04/2022, 6:25 AM
    Im using a
    Flow<T>
    which queries data from an SQL database, initially emitting the current version of the data, and then emitting again anytime it changes. Is it possible to collect the flow in a way such that the initial query isnt re-ran everytime I restart collecting it? Consider a component that can be started/stopped, if the initial query has finished it wouldnt make sense to re-run it the next time the component is started, Id much prefer to just receive any further changes to it. The natural approach seems to be collecting the flow in start, cancelling the resulting job in stop - as expected that restarts the collection everytime though.
    g
    • 2
    • 8
  • a

    Alexander Maryanovsky

    03/04/2022, 8:14 AM
    Reading the “Kotlin Coroutines” book, it just dawned on me that
    CoroutineContext
    is basically the coroutines’ equivalent of
    ThreadLocal
    . Why don’t the tutorials say this? Would make understanding so much faster…
    :mind-blown: 2
    s
    • 2
    • 9
  • t

    theapache64

    03/04/2022, 10:25 AM
    ✅ SOLVED It prints the response, but the scope is not getting finished (or the program doesn’t exit after the response).. What am i missing here? 🧵
    s
    • 2
    • 4
  • b

    bogdoll

    03/05/2022, 1:40 PM
    I have some legacy business logic which I would like to refactor to use coroutines. I have one central datastructure which is currently protected by a synchronous block. I could replace the synchronous block also with a mutex. But both mutex and synchronous blocks are not compatible with coroutines, or? So what would be the coroutine way to access a datastructure which should not be accessed in a multithreaded way.
    j
    • 2
    • 5
  • b

    bogdoll

    03/06/2022, 7:18 AM
    Is there a coroutine API for working with the file system and sockets? When I looked on search.maven.org I found this:
    <dependency>
      <groupId>org.jetbrains.kotlinx</groupId>
      <artifactId>kotlinx-coroutines-nio</artifactId>
      <version>0.26.1-eap13</version>
    </dependency>
    But could not find it on the official kotlin pages. Additional the version numbers looks suspect.
    y
    • 2
    • 2
  • y

    yschimke

    03/06/2022, 9:57 AM
    How far can you get with the coroutines classes in stdlib, without kotlinx.coroutines? From https://stackoverflow.com/questions/58237116/kotlin-coroutines-possible-without-standard-library it seems technically possible but likely a dangerous path. But for an existing callback API with internal threading, is there a viable path to implement a suspend function without kotlinx.coroutines? It would also need to support cancellation in both directions.
    j
    • 2
    • 7
  • u

    ursus

    03/06/2022, 9:59 PM
    suspend fun <T> ???(millis: Long, action: suspend () -> T): T {
        val start = System.currentMillis()
        val value = action()
        val end = System.currentMillis()
        val took = end - start
        if (took < millis) {
            delay(millis - took)
        }
        return value
    }
    does this have a name already? it makes sure the suspend function takes atleast N millis, so its not too fast its not debounce since its calcualtes the delay …so I guess I need my own name
    r
    e
    m
    • 4
    • 8
  • u

    ursus

    03/06/2022, 11:51 PM
    Is using
    flow.first { .. }
    to suspend till a value I want is emitted and then continue in “suspend functions world” with my logic more idiomatic than
    take(1)
    (and keeping the logic reactive)? or rather is there some technical difference between
    flow.filter { … }
    and
    flow.first { .. }
    ? I mean they both “wait” by suspending the coroutine, right?
    s
    d
    • 3
    • 9
  • a

    Aditya Wasan

    03/09/2022, 11:34 AM
    Hey everyone, I have a query around testing a suspend function in which I am doing some stuff using the external scope.
    public suspend fun saveAuthToken(authToken: String): Result<String, Throwable> {
        return runCatching {
          require(authToken.isNotEmpty()) { "authToken cannot be empty" }
    
          val authTokenKey = stringPreferencesKey(AUTH_TOKEN_KEY)
          // Use the external scope here to save the auth token
          externalScope
            .launch(ioDispatcher) { authDataStore.edit { store -> store[authTokenKey] = authToken } }
            .join()
    
          authToken
        }
      }
    This is the code that launches a new coroutine in an external scope which will can live longer than the suspend function. Now, to test this behavior I’m assuming that I would have to pause the external coroutine and then cancel the scope in which the suspend function is called. After that, I can resume the external coroutine and then check the authDataStore to verify. However, I couldn’t find any standard way to do this. Does anyone know any resource or samples where I could look at a similar behavior. Thanks in advance.
    e
    y
    +2
    • 5
    • 8
  • b

    brabo-hi

    03/09/2022, 9:44 PM
    hi all, how come on coroutines-code lib i don’t have
    IO dispatcher
    package kotlinx.coroutines
    
    import kotlin.coroutines.*
    
    public actual object Dispatchers {
        public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
        public actual val Main: MainCoroutineDispatcher = JsMainDispatcher(Default, false)
        public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
    }
    e
    t
    • 3
    • 4
  • e

    Exerosis

    03/15/2022, 4:49 AM
    class Test {
        val continuation = Continuation<Unit>(EmptyCoroutineContext) {
        }
    
        suspend fun testFire() = suspendCoroutineUninterceptedOrReturn<Unit> { cont ->
            println(continuation)
            println(cont)
            println(continuation == cont)
        }
        fun testMain() {
            val action: suspend () -> (Unit) = {
                testFire()
            }
            action.startCoroutineUninterceptedOrReturn(continuation)
        }
    }
    Why does this not not print the same thing twice and then true? IK it's significantly stupid but it still wasn't what I was expecting. And more generally is there a way to (safely) inject some sort of identifier into a continuation that can be picked up later? I assume Context is the right thing to use but I don't want to like overwrite any other context like job.
    n
    • 2
    • 1
  • z

    ziv kesten

    03/15/2022, 3:45 PM
    I am trying to test a class that is a
    CoroutineScope
    and is testing a flow emitted from a fake implementation of another class. However, the flow does not seem to emit the data i assign it to stream. What would you think the problem might be?
    private val mainThreadSurrogate = newSingleThreadContext("UI thread")
    
        @Before
        fun setUp() {
            Dispatchers.setMain(mainThreadSurrogate)
        }
    
        @After
        fun tearDown() {
            Dispatchers.resetMain() // reset the main dispatcher to the original Main dispatcher
            mainThreadSurrogate.close()
        }
    
        @Test
        fun testTest(): Unit = runBlocking {
            launch(Dispatchers.Main) {
                val recorder = FakeRecordingsHandler()
                val sut = SomeClass(recorder, this.coroutineContext)
                recorder.currentTrack = flow { emit(DEMO_TRACK) }
                val isCurrentTrack = sut.isCurrentTrack(DEMO_TRACK.id)
                assertTrue(isCurrentTrack)
            }
        }
    //    class SomeClass(
    //        private val recorder: RecordingsHandler,
    //        override val coroutineContext: CoroutineContext
    //    ) : VoiceRecorder, CoroutineScope {
    //
    //        private var currentTrackId: String? by mutableStateOf(null)
    //
    //        init {
    //            launch {
    //                recorder.currentTrack.distinctUntilChanged().collect { demoTrack ->
    //                    currentTrackId = demoTrack?.id
    //            }
    //         }
    //
    //        override fun isCurrentTrack(id: String) = currentTrackId == id
    //    }
    n
    • 2
    • 4
  • m

    Marin Tolić

    03/15/2022, 6:39 PM
    Hey folks, I've been having a bit of trouble with testing and I'm wondering if any of you know the cause. Currently we have this in our VM:
    public val channel: LiveData<Channel> = channelState.channelData.combine(channelState.members) { _, _ ->
            channelState.toChannel()
        }.asLiveData()
    We do in fact know that this does work when the app is run, but want to write a simple test to ensure this doesn't break in the future.
    @OptIn(ExperimentalCoroutinesApi::class)
        @Test
        fun `When channel members update should update channel`() = runBlockingTest {
            val messageListHeaderViewModel = MessageListHeaderViewModel(CID, chatClient = chatClient)
            val mockObserver: Observer<Channel> = spy()
            messageListHeaderViewModel.channel.observeForever(mockObserver)
    
            val memberList = createMembers { createMember(User(name = "user")) }
            membersMock.emit(memberList)
            advanceUntilIdle()
    
            verify(mockObserver, times(2)).onChanged(any())
        }
    The issue is that the observer will update only once which is the first emission of
    Channel
    triggered by
    SharingStarted.Eagerly
    . Curiously enough, if you collect the Flow in tests without the
    .asLiveData()
    transformation, you will collect all of it including the latest update. However then transforming to
    LiveData
    no interactions with the mock can be observed. Even more curiously the same
    .asLiveData()
    transformed variable is testable if no
    combine
    is called. So if we just had
    channelState.members.asLiveData()
    we could observe all of the emissions from it without any issue. I've been trying to wrap my head around this for a while and can't understand what gives. Thanks in advance for any assistance 😄
    t
    • 2
    • 1
Powered by Linen
Title
m

Marin Tolić

03/15/2022, 6:39 PM
Hey folks, I've been having a bit of trouble with testing and I'm wondering if any of you know the cause. Currently we have this in our VM:
public val channel: LiveData<Channel> = channelState.channelData.combine(channelState.members) { _, _ ->
        channelState.toChannel()
    }.asLiveData()
We do in fact know that this does work when the app is run, but want to write a simple test to ensure this doesn't break in the future.
@OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun `When channel members update should update channel`() = runBlockingTest {
        val messageListHeaderViewModel = MessageListHeaderViewModel(CID, chatClient = chatClient)
        val mockObserver: Observer<Channel> = spy()
        messageListHeaderViewModel.channel.observeForever(mockObserver)

        val memberList = createMembers { createMember(User(name = "user")) }
        membersMock.emit(memberList)
        advanceUntilIdle()

        verify(mockObserver, times(2)).onChanged(any())
    }
The issue is that the observer will update only once which is the first emission of
Channel
triggered by
SharingStarted.Eagerly
. Curiously enough, if you collect the Flow in tests without the
.asLiveData()
transformation, you will collect all of it including the latest update. However then transforming to
LiveData
no interactions with the mock can be observed. Even more curiously the same
.asLiveData()
transformed variable is testable if no
combine
is called. So if we just had
channelState.members.asLiveData()
we could observe all of the emissions from it without any issue. I've been trying to wrap my head around this for a while and can't understand what gives. Thanks in advance for any assistance 😄
t

tseisel

03/15/2022, 10:47 PM
I think it's related to the fact that unlike Flow, updates to the value of a LiveData are only notified on Android's main thread. Unit Tests are not run on the main thread by default. You may need to use
InstantTaskExecutorRule
when testing code using
LiveData
.
View count: 6