Restartable count down in Kotlin - kotlin

I try to implement a restartable count down in pure Kotlin (without CountDownTimer from Android SDK)
I got inspired from How to create a simple countdown timer in Kotlin?
I adapted it because I want coroutine scope managed by caller.
I add a filter in initTimer flow to stop and restart countdown when coroutine is cancelled but it doesn't work, count down continue and not restarted, when I call toggleTime before last is not finished.
class CountDownTimerUseCase {
private val _countDown = MutableStateFlow(0)
val countDown: StateFlow<Int> = _countDown
private var countDownTimerJob: Job? = null
suspend fun toggleTime(totalSeconds: Int) {
coroutineScope {
countDownTimerJob?.cancel()
countDownTimerJob = launch {
initTimer(this, totalSeconds)
.cancellable()
.onCompletion { _countDown.emit(0) }
.collect { _countDown.emit(it) }
}
}
}
/**
* The timer emits the total seconds immediately.
* Each second after that, it will emit the next value.
*/
private suspend fun initTimer(coroutineScope: CoroutineScope, totalSeconds: Int): Flow<Int> =
(totalSeconds - 1 downTo 0)
.filter {
//coroutineContext[Job]?.isActive == true
coroutineScope.isActive
}
.asFlow() // Emit total - 1 because the first was emitted onStart
.cancellable()
.onEach { delay(1000) } // Each second later emit a number
.onStart { emit(totalSeconds) } // Emit total seconds immediately
.conflate() // In case the operation onTick takes some time, conflate keeps the time ticking separately
.transform { remainingSeconds: Int ->
emit(remainingSeconds)
}
}
Here the junit test :
class CountDownTimerUseCaseTest {
private val countDownTimerUseCase = CountDownTimerUseCase()
#Test
fun `WHEN count down timer re-start THEN get re-initialized tick`() = runTest{
countDownTimerUseCase.countDown.test {
//init value
var tick = awaitItem()
assertEquals(0, tick)
//start count down
countDownTimerUseCase.toggleTime(30)
// not loop until 0 to be sure cancel is done before the end
for (i in 30 downTo 1) {
tick = awaitItem()
println(tick)
if(tick==0) {
//re-start has be done
break
}
assertEquals(i, tick)
if(i==30) {
println("relaunch")
countDownTimerUseCase.toggleTime(30)
}
}
// check tick after restart
for (i in 30 downTo 0) {
tick = awaitItem()
println(tick)
assertEquals(i, tick)
}
}
}
}

Solution in comment from #Tenfour04 works, thanks
class CountDownTimerUseCase {
private val _countDown = MutableStateFlow(0)
val countDown: StateFlow<Int> = _countDown
private var countDownTimerJob: Job? = null
fun toggleTime(scope: CoroutineScope, totalSeconds: Int) {
countDownTimerJob?.cancel()
countDownTimerJob = initTimer(totalSeconds)
.onEach { _countDown.emit(it) }
.onCompletion { _countDown.emit(0) }
.launchIn(scope)
}
/**
* The timer emits the total seconds immediately.
* Each second after that, it will emit the next value.
*/
private fun initTimer(totalSeconds: Int): Flow<Int> =
flow {
for (i in totalSeconds downTo 1) {
emit(i)
delay(1000)
}
emit(0)
}.conflate()
}
And unit-tests:
class CountDownTimerUseCaseTest {
private val countDownTimerUseCase = CountDownTimerUseCase()
#Test
fun `WHEN count down timer start THEN get tick`() = runTest {
countDownTimerUseCase.countDown.test {
//init value
var tick = awaitItem()
assertEquals(0, tick)
countDownTimerUseCase.toggleTime(this#runTest, 30)
for (i in 30 downTo 0) {
tick = awaitItem()
assertEquals(i, tick)
}
}
}
#Test
fun `WHEN count down timer re-start THEN get re-initialized tick`() = runTest{
countDownTimerUseCase.countDown.test {
//init value
var tick = awaitItem()
assertEquals(0, tick)
//start count down
countDownTimerUseCase.toggleTime(this#runTest, 30)
// not loop until 0 to be sure cancel is done before the end
for (i in 30 downTo 1) {
tick = awaitItem()
if(tick==0) {
//re-start has be done
break
}
assertEquals(i, tick)
if(i==30) {
countDownTimerUseCase.toggleTime(this#runTest, 30)
}
}
// check tick after restart
for (i in 30 downTo 0) {
tick = awaitItem()
assertEquals(i, tick)
}
}
}
}

Related

Can I collect data from a Flow every two seconds in Kotlin?

I use Code A to colloet data from the Flow timeXFlow.
I hope to collect the data every two seconds, how can I do ?
Code A
val _timeX = MutableStateFlow(0)
viewModelScope.launch {
timeXFlow.collect {
_timeX.value = it
}
}
val timeXFlow: Flow<Int> = flow {
var i = 0
while (true) {
emit(i)
i = toDosome( ) // I can't estimate the time
delay(1000)
}
}
#Composable
fun UI(){
//I hope that xTime can be updated per 2 sec, so UI can repaint with new data.
val xTime by _timeX.collectAsState()
...
}
Added Content
To Arpit Shukla: Thanks!
The Code B is based your thinking, but your way will lost many emitting datas.
Code B
val _timeX = MutableStateFlow(0)
private var aa=0
viewModelScope.launch {
coroutineScope {
launch {
while (true) {
_timeX.value = aa
delay(2000)
}
}
launch {
timeXFlow.collect {
aa = it
}
}
}
}
//The same with Code A
A simple solution that comes to my mind is:
viewModelScope.launch {
var i = 0
launch {
while(true) {
updateUI(i)
delay(2000)
}
}
timeXFlow.collect {
i = it
}
}

How can I design a Flow which is a average value of every latest 5 data of another Flow?

There is a Flow which will emit data every 100ms, and I hope to get a average value of every latest 5 data of the Flow, and convert the average value as Double value into another Flow.
How can I design the Flow?
Code A
fun soundDbFlow(period: Long = 100) = flow {
while (true) {
var data = getAmplitude()
emit(data)
delay(period)
}
}
.get_Average_Per5_LatestData {...} //How can I do? or is there other way?
.map { soundDb(it) }
private fun getAmplitude(): Int {
var result = 0
mRecorder?.let {
result = it.maxAmplitude
}
return result
}
private fun soundDb(input:Int, referenceAmp: Double = 1.0): Double {
return 20 * Math.log10(input / referenceAmp)
}
Added Content:
To plplmax: Thanks!
I assume Code B will emit 1,2,3,4,5,6,7,8,9,10.....
Do you guarantee Code C will calculate (1+2+3+4+5)/5 first, then calculate (6+7+8+9+10)/5 second, .... ? It's my expectation.
I worry that Code C maybe calculate (1+2+3+4+5)/5 first, the calculate (2+3+4+5+6)/5 sencond, ...
Code B
suspend fun soundDbFlow(period: Long) = flow {
while (true) {
val data = getAmplitude()
emit(data)
delay(period)
}
}
Code C
private fun reduceFlow(period: Long = 100) = flow {
while (true) {
val result = soundDbFlow(period)
.take(5)
.map { soundDb((it / 5.0).roundToInt()) }
.reduce { accumulator, value -> accumulator + value }
emit(result)
}
}
You can write a chunked operator like this:
/**
* Returns a Flow that emits sequential [size]d chunks of data from the source flow,
* after transforming them with [transform].
*
* The list passed to [transform] is transient and must not be cached.
*/
fun <T, R> Flow<T>.chunked(size: Int, transform: suspend (List<T>)-> R): Flow<R> = flow {
val cache = ArrayList<T>(size)
collect {
cache.add(it)
if (cache.size == size) {
emit(transform(cache))
cache.clear()
}
}
}
And then use it like this:
suspend fun soundDbFlow(period: Long) = flow {
while (true) {
val data = getAmplitude()
emit(data)
delay(period)
}
}
.chunked(5) { (it.sum() / 5.0).roundToInt() }
.map { soundDb(it) }
Is that what you want?
var result = 0
fun soundDbFlow(period: Long) = flow {
while (true) {
delay(period)
val data = getAmplitude()
emit(data)
}
}
fun reduceFlow(period: Long = 100) = flow {
while (true) {
val sum = soundDbFlow(period)
.take(5)
.reduce { accumulator, value -> accumulator + value }
val average = (sum / 5.0).roundToInt()
emit(soundDb(average))
}
}
fun getAmplitude(): Int {
return ++result
}
fun soundDb(input: Int, referenceAmp: Double = 1.0): Double {
return 20 * log10(input / referenceAmp)
}
fun main(): Unit = runBlocking {
reduceFlow().collect { println(it) }
}

Testing Kotlin Coroutines (Flow vs Publisher)

I have a reasonably simple bit of code - every second ping some handler with the current timestamp:
private val clock = Clock.systemDefaultZone()
private fun now() = clock.instant()
suspend fun ping(pinger: suspend (Instant) -> Unit) {
repeat(5) {
pinger(now())
if (it < 4) {
delay(1.seconds)
}
}
}
Which I want to expose as a reactive Producer:
fun publishPing() = publish { ping(::send) }
In practice, it works - but when testing:
#OptIn(ExperimentalCoroutinesApi::class)
#Test
fun `test publishPing`() = runTest {
var count = 0
publishPing().collect {
count++
assertTrue { EPOCH.isBefore(it) }
assertTrue { it.isBefore(now()) }
}
assertEquals(5, count) // Pass
assertEquals(4000, currentTime) // AssertionFailedError: Expected:4000; Actual:0
}
The virtual time doesn't get incremented, and he test takes 4+ seconds to run (eg the calls to delay() aren't being handled by the test dispatcher).
Doing the same with a flow all works as expected (and runs in milliseconds).
fun flowPing() = flow { ping(::emit) }
#OptIn(ExperimentalCoroutinesApi::class)
#Test
fun `test flowPing`() = runTest {
var count = 0
flowPing().collect {
count++
assertTrue { EPOCH.isBefore(it) }
assertTrue { it.isBefore(now()) }
}
assertEquals(5, count) // Pass
assertEquals(4000, currentTime) // Pass
}
I have a vague idea of what is [not] going on (the coroutines-test support isn't Hooked into the coroutines/reactive support?) - but what do I need to do to sort it?
FWIW the same behaviour happens with coroutines 1.5.x and runBlockingTest (code above from coroutines 1.6 and runTest)

Fan-out / fan-in - closing result channel

I'm producing items, consuming from multiple co-routines and pushing back to resultChannel. Producer is closing its channel after last item.
The code never finishes as resultChannel is never being closed. How to detect and properly finish iteration so hasNext() return false?
val inputData = (0..99).map { "Input$it" }
val threads = 10
val bundleProducer = produce<String>(CommonPool, threads) {
inputData.forEach { item ->
send(item)
println("Producing: $item")
}
println("Producing finished")
close()
}
val resultChannel = Channel<String>(threads)
repeat(threads) {
launch(CommonPool) {
bundleProducer.consumeEach {
println("CONSUMING $it")
resultChannel.send("Result ($it)")
}
}
}
val iterator = object : Iterator<String> {
val iterator = resultChannel.iterator()
override fun hasNext() = runBlocking { iterator.hasNext() }
override fun next() = runBlocking { iterator.next() }
}.asSequence()
println("Starting interation...")
val result = iterator.toList()
println("finish: ${result.size}")
You can run a coroutine that awaits for the consumers to finish and then closes the resultChannel.
First, rewrite the code that starts the consumers to save the Jobs:
val jobs = (1..threads).map {
launch(CommonPool) {
bundleProducer.consumeEach {
println("CONSUMING $it")
resultChannel.send("Result ($it)")
}
}
}
And then run another coroutine that closes the channel once all the Jobs are done:
launch(CommonPool) {
jobs.forEach { it.join() }
resultChannel.close()
}

Dead man's switch in Kotlin

I want to implement a Dead man's switch in Kotlin. What this does is fire a notification TIME_INTERVAL seconds after the last MyEvent was received. When a new MyEvent is received, it restarts the timer.
private val stopWatch = object : () -> Unit {
var timer = System.currentTimeMillis()
var isRunning = false
override fun invoke() {
timer = System.currentTimeMillis()
if (isRunning) return
synchronized(this) {
isRunning = true
while (System.currentTimeMillis() - timer <= TIME_INTERVAL) {}
fireNotification()
isRunning = false
}
}
}
override fun onSomeEvent(e: MyEvent?) {
runAsync(stopWatch)
}
Is there a simpler or easier way to get this functionality by using either the kotlin.concurrent or Java standard libraries?
If I understand correctly, your code consists in looping doing nothing until the time interval has elapsed. This is a bad idea, since it consumes a whole lot of CPU doing nothing, instead of just waiting.
I would use a ScheduledExecutr to schedule the firing of the notification. And I would cancel the returned future when an event comes in before the notification is fired:
import java.time.Instant.now
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
class StopWatch {
private var future: ScheduledFuture<Void>? = null;
private val executor = Executors.newSingleThreadScheduledExecutor()
fun onSomeEvent() {
synchronized(this) {
future.let {
future?.cancel(false)
future = null
}
val command = {
synchronized(this#StopWatch) {
future = null
}
fireNotification()
null
}
future = executor.schedule(command, 2, TimeUnit.SECONDS)
}
println("${now()} - event")
}
private fun fireNotification() {
println("${now()} - notification")
}
fun shutdown() {
executor.shutdown()
}
}
fun main(args: Array<String>) {
val stopWatch = StopWatch()
stopWatch.onSomeEvent()
Thread.sleep(1000)
stopWatch.onSomeEvent()
Thread.sleep(1000)
stopWatch.onSomeEvent()
Thread.sleep(1000)
stopWatch.onSomeEvent()
Thread.sleep(3000)
stopWatch.onSomeEvent()
stopWatch.shutdown()
}
Which prints:
2017-05-07T12:45:55.647Z - event
2017-05-07T12:45:56.741Z - event
2017-05-07T12:45:57.743Z - event
2017-05-07T12:45:58.745Z - event
2017-05-07T12:46:00.747Z - notification
2017-05-07T12:46:01.750Z - event
2017-05-07T12:46:03.753Z - notification