Suspend a call until a value is set - kotlin

I want class UploadWorker to retrieve a value from class Manager, but that value may not yet be ready in Manager. So I want class UploadWorker to wait until that value is set.
class UploadWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
Manager.isReady()
return Result.success()
}
}
object Manager {
private lateinit var isReady
fun initialize(context: Context, myData: MyData) {
...
isReady = true
}
suspend fun isReady() {
if(::isReady.isInitialized()
return isReady
else // wait here until initialized
}
}
In the else if I could somehow suspend or wait until my MyApplication class calls initialize(). Any ideas?

CompletableDeferred is quite handy for situations like this.
Your code would probably look something like this:
object Manager {
private val initialized = CompletableDeferred<Unit>()
fun initialize(context: Context, myData: MyData) {
...
initialized.complete(Unit)
}
suspend fun awaitInitialized() {
initialized.await()
// initialization is done at this point
}
}

You can use StateFlow to deliver status of initialization:
val isReady = MutableStateFlow(false)
// to wait:
if (isReady.value) return true
else isReady.first { it } // it == true
// to notify:
isReady.value = true
Although StateFlows are quite lightweight, but if you want to do it more lighter but in a ugly way (using coroutine internals directly):
val conts = mutableListOf<Continuation<Boolean>>()
private lateinit var isReady = false
set(value) {
if (value) conts.forEach { it.resume(true) }.also { conts.clear() }
field = value
}
// to wait:
if (isReady) return true
else suspendCancellableCoroutine { cont ->
conts.add(cont)
invokeOnCancellation { conts.remove(cont) }
}
// to notify:
isReady = true

Related

how to test project reactor code that does not return a subscription

i'm trying to create a component that stream data from remote service continuously. The component starts and stops according to spring container lifecycle. I'm not sure how to test this component as the subscription is done inside my component so i was wondering wether this is the correct way to implement this kind of component with webflux or not. Does anybody know any similar component in any framework from where i might take some ideas?
Regards
class StreamingTaskAdapter(
private val streamEventsUseCase: StreamEventsUseCase,
private val subscriptionProperties: subscriptionProperties,
) : SmartLifecycle, DisposableBean, BeanNameAware {
private lateinit var disposable: Disposable
private var running: Boolean = false
private var beanName: String = "StreamingTaskAdapter"
private val logger = KotlinLogging.logger {}
override fun start() {
logger.info { "Starting container with name $beanName" }
running = true
doStart()
}
private fun doStart() {
disposable = Mono.just(
CreateSubscriptionCommand(
subscriptionProperties.events,
subscriptionProperties.owningApplication
)
)
.flatMap(streamEventsUseCase::createSubscription)
.flatMap { subscription ->
Mono.just(subscription)
.map(::ConsumeSubscriptionCommand)
.flatMap(streamEventsUseCase::consumeSubscription)
}
.repeat()
.retryWhen(Retry.backoff(MAX_ATTEMPTS, Duration.ofSeconds(2)).jitter(0.75))
.doOnSubscribe { logger.info { "Started event streaming" } }
.doOnTerminate { logger.info { "Stopped event streaming" } }
.subscribe()
}
override fun stop() {
logger.info("Stopping container with name $beanName")
doStop()
}
override fun isRunning(): Boolean = running
private fun doStop() {
running = false
disposable.dispose()
}
override fun destroy() {
logger.info("Destroying container with name $beanName")
doStop()
}
override fun setBeanName(name: String) {
this.beanName = name
}
companion object {
const val MAX_ATTEMPTS: Long = 3
}
}

How to subscribe to StateFlow in kotlin-react useEffect

I'm trying to create a small counter example for kotlin-react with functionalComponent with kotlin 1.4-M2.
The example should use kotlinx.coroutines.flow. I'm struggling at collecting the values from the store in reacts useEffect hook.
Store:
object CounterModel { // Modified sample from kotlin StateFlow doc
private val _counter = MutableStateFlow(0) // private mutable state flow
val counter: StateFlow<Int> get() = _counter // publicly exposed as read-only state flow
fun inc() { _counter.value++ }
}
Component:
val counter = functionalComponent<RProps> {
val (counterState, setCounter) = useState(CounterModel.counter.value)
useEffect(listOf()) {
// This does not work
GlobalScope.launch { CounterModel.counter.collect { setCounter(it) } }
}
div {
h1 {
+"Counter: $counterState"
}
button {
attrs.onClickFunction = { CounterModel.inc() }
}
}
}
When I directly call CounterModel.counter.collect { setCounter(it) } it complains about Suspend function 'collect' should be called only from a coroutine or another suspend function.
How would you implement this useEffect hook?
And once the subscription works, how would you unsubscribe from it (use useEffectWithCleanup instead of useEffect)?
Finally found a solution. We can use onEach to do an action for every new value and then 'subscribe' with launchIn. This returns a job that can be canceled for cleanup:
object CounterStore {
private val _counter = MutableStateFlow(0)
val counter: StateFlow<Int> get() = _counter
fun inc() { _counter.value++ }
}
val welcome = functionalComponent<RProps> {
val (counter, setCounter) = useState(CounterStore.counter.value)
useEffectWithCleanup(listOf()) {
val job = CounterStore.counter.onEach { setCounter(it) }.launchIn(GlobalScope)
return#useEffectWithCleanup { job.cancel() }
}
div {
+"Counter: $counter"
}
button {
attrs.onClickFunction = { CounterStore.inc() }
+"Increment"
}
}
We can extract this StateFlow logic to a custom react hook:
fun <T> useStateFlow(flow: StateFlow<T>): T {
val (state, setState) = useState(flow.value)
useEffectWithCleanup(listOf()) {
val job = flow.onEach { setState(it) }.launchIn(GlobalScope)
return#useEffectWithCleanup { job.cancel() }
}
return state
}
And use it like this in our component:
val counter = useStateFlow(CounterStore.counter)
The complete project can be found here.
The Flow-Api is very experimental so this might not be the final solution :)
if's very important to check that the value hasn't changed,
before calling setState, otherwise the rendering happens twice
external interface ViewModelProps : RProps {
var viewModel : MyViewModel
}
val App = functionalComponent<ViewModelProps> { props ->
val model = props.viewModel
val (state, setState) = useState(model.stateFlow.value)
useEffectWithCleanup {
val job = model.stateFlow.onEach {
if (it != state) {
setState(it)
}
}.launchIn(GlobalScope)
return#useEffectWithCleanup { job.cancel() }
}
}

how to write getter and setter containing mutex.withLock in kotlin

I'd like to have a synchronised access to a variable that represents my state from coroutines. How can I fix this?
private var myState: MyState? = null
get() = mutex.withLock {
return#withLock myState
}
set(value) = mutex.withLock {
field = value
}
private val mutex = Mutex()
right now I get Suspend function 'withLock' should be called only from a coroutine or another suspend function message.
If not possible any alternative elegant solutions?
To call suspend function in a non-suspending context. You can use runBlocking.
private var myState: MyState? = null
get() {
return runBlocking {
mutex.withLock {
myState
}
}
}
set(value) {
runBlocking {
mutex.withLock {
field = value
}
}
}
private val mutex = Mutex()
NOTES:
You might be better off changing the property to two suspend functions (getter/setter), instead of using runBlocking.
All depends on the context in which you call myState.
You also want to consider voting for KT-15555.

Why is the getValue of LiveData<Long> Nullable?

The Code A is based https://github.com/android/architecture-components-samples/blob/master/LiveDataSample/app/src/main/java/com/android/example/livedatabuilder/LiveDataViewModel.kt
I modified some code.
I think that emit(timeStampToTime(currentTime.value)) will be correct, but the system report error, so I have to use emit(timeStampToTime(currentTime.value?:0))
1: I'm very strange that why val currentTime isn't Nullable and currentTime.value is Nullable .
2: Why does the original code use liveData { emit(timeStampToTime(it)) } instead of liveData { emit(timeStampToTime(it?:0)) }
Code A
class LiveDataViewModel(
private val dataSource: DataSource
) : ViewModel() {
val currentTime = dataSource.getCurrentTime()
val currentTimeTransformed: LiveData<String> = liveData {
//emit(timeStampToTime(currentTime.value)) // It will cause error
emit(timeStampToTime(currentTime.value?:0))
}
//Original Code
// Coroutines inside a transformation
// val currentTimeTransformed = currentTime.switchMap {
// timeStampToTime is a suspend function so we need to call it from a coroutine.
// liveData { emit(timeStampToTime(it)) }
// }
private suspend fun timeStampToTime(timestamp: Long): String {
delay(500) // Simulate long operation
val date = Date(timestamp)
return date.toString()
}
}
class DefaultDataSource(private val ioDispatcher: CoroutineDispatcher) : DataSource {
override fun getCurrentTime(): LiveData<Long> =
liveData {
while (true) {
emit(System.currentTimeMillis())
delay(1000)
}
}
...
}
It's nullable because there is a constructor
LiveData()
Creates a LiveData with no value assigned to it.
and calling e.g.
val data: LiveData<Long> = LiveData()
data.value
will return null.
Or to consider your code:
liveData {
while (true) {
emit(System.currentTimeMillis())
delay(1000)
}
}
You emit a value immediately, but if you change the order
liveData {
while (true) {
delay(1000)
emit(System.currentTimeMillis())
}
}
then there is no value emitted during the first second and calling currentTime.value will return null.
(Note: is it intentional that you create a new LiveData on every currentTime access?)

Polling repository with coroutine channels

I'm playing with coroutine channels and I wanted to implemented a polling test project. The idea is that a viewmodel will listen for data from a repository that polls an endpoint repeatedly.
When I pass a coroutineScope to the repository, the polling works, however when I create a new coroutineSCope in the repository, I see the data being injected into the channel, but it's not received on the viewmodel.
So this works:
class PollingViewModel : ViewModel() {
val counter = MutableLiveData<String>().apply { value = "uninitialized" }
private val repository = Repository()
init {
viewModelScope.launch {
val channel = repository.poll(this /* scope */)
channel.consumeEach {
Log.d("foo", "Viewmodel received [$it]")
counter.postValue(it.toString())
}
}
}
}
class Repository {
private var startValue = 0
suspend fun poll(coroutineScope: CoroutineScope) =
coroutineScope.produce(capacity = Channel.CONFLATED) {
while (true) {
Log.d("foo", "Sending value [$startValue]")
send(startValue++)
delay(POLLING_PERIOD_MILLIS)
}
}
companion object {
private const val POLLING_PERIOD_MILLIS = 1000L
}
}
But this does not (viewmodel does not receive anything):
class PollingViewModel : ViewModel() {
val counter = MutableLiveData<String>().apply { value = "uninitialized" }
private val repository = Repository()
init {
viewModelScope.launch {
repository.poll().consumeEach {
Log.d("foo", "Viewmodel received [$it]")
counter.postValue(it.toString())
}
}
}
}
class Repository {
private var startValue = 0
suspend fun poll() = coroutineScope {
produce(capacity = Channel.CONFLATED) {
while (true) {
Log.d("foo", "Sending value [$startValue]")
send(startValue++)
delay(POLLING_PERIOD_MILLIS)
}
}
}
companion object {
private const val POLLING_PERIOD_MILLIS = 1000L
}
}
What is the issue with creating a coroutineScope at the repository level?
Looks like the solution is to create a new CoroutineContext in the repository:
class Repository {
private var startValue = 0
private val context: CoroutineContext by lazy(LazyThreadSafetyMode.NONE) {
Job() + Dispatchers.IO
}
suspend fun poll(): ReceiveChannel<Int> = coroutineScope {
produce(
context = context,
capacity = Channel.CONFLATED
) {
while (true) {
send(startValue++)
delay(POLLING_PERIOD_MILLIS)
}
}
}
companion object {
private const val POLLING_PERIOD_MILLIS = 1000L
}
}