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

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) }
}

Related

Read value from Kotlin flow and return immediately

I have to read a value from a flow just once and then immediately return it from the function. I have a piece of code like this:
fun getValue(): String {
val flow = getFlow()
Mainscope().launch {
flow.collect {
return it
}
}
}
But this is giving me an error saying that I cant return from inside collect. I know that ideally I should be returning the flow object itself and then calling collect on the returned flow. But it is not possible, since getValue() has already been used in several places by now and I cannot change its signature now.
I have tried using suspend and synchronized as follows:
// call the function like this: runBlocking { print(getValue()) }
suspend fun getValue(): String {
val flow = getFlow()
flow.collect {
return it
}
}
and
fun getValue(): String {
val lock = Any()
var value: String? = null
val flow = getFlow()
MainScope().launch {
flow.collect {
synchronized(lock) {
value = it.toString()
lock.notify()
}
}
}
synchronized(lock) {
while (value == null) lock.wait()
return value as String
}
}
But in both cases the control never reaches inside collect. So I tried putting collect inside a new thread:
...
val flow = getFlow()
thread {
MainScope().launch {
flow.collect {
synchronized(lock) {
value = it.toString()
lock.notify()
}
}
}
}
synchronized(lock) {
...
but its still the same. So how do I read the value from the flow in a non-suspending way and return it immediately?
To get first value of the flow:
fun getFlow() = flowOf("one","two","three")
fun getValue(): String {
var r = ""
runBlocking {
r = getFlow().firstOrNull()?:"none"
}
return r
}
println(getValue())
//one
To get last value of the flow:
fun getValue(): String {
var r = ""
runBlocking {
getFlow().collect {
r = it
}
}
return r
}
println(getValue())
//three

Can I use one Job instead of two Jobs when I use Flow in Kotlin?

In Code A, there are two Flows, and I assign two jobs for them, I collect the two Flows in fun beginSoundDensity() and stop collecting the two Flows in fun resetSoundDensity().
I think there are many repeated codes in Code A, so I hope to improve it, but Code B doesn't work.
Can I use one Job in my case?
Code A
private val _soundDensityState = MutableStateFlow(initialMSoundDensity)
val soundDensityState = _soundDensityState.asStateFlow()
private val _timeX = MutableStateFlow(0)
val timeX = _timeX.asStateFlow()
private var myJob1: Job?=null
private var myJob2: Job?=null
val myFlow: Flow<Int> = flow {
var i = 0
while (true) {
emit(i)
i = i + 15
delay(5000)
}
}
fun beginSoundDensity() {
myJob1?.cancel()
myJob2?.cancel()
myJob1 = viewModelScope.launch {
aSoundMeter.startSoundDensity {
pauseSoundDensity()
}.cancellable()
.collect {
_soundDensityState.value = it
}
}
myJob2 = viewModelScope.launch {
myFlow.collect {
_timeX.value = it
}
}
}
}
fun resetSoundDensity(){
myJob1?.cancel()
myJob2?.cancel()
}
Code B
//The same
private var myJob: Job?=null
val myFlow: Flow<Int> = flow {
var i = 0
while (true) {
emit(i)
i = i + 15
delay(5000)
}
}
fun beginSoundDensity() {
myJob?.cancel()
myJob = viewModelScope.launch {
aSoundMeter.startSoundDensity {
pauseSoundDensity()
}.cancellable()
.collect {
_soundDensityState.value = it
}
myFlow.collect {
_timeX.value = it //It will not be launched
}
}
}
}
fun resetSoundDensity(){
myJob?.cancel()
}
Yes and no. You need two separate coroutines running concurrently to collect from two flows. In your Code B myFlow will be collected only after aSoundMeter finishes collecting. Collections need to run at the same time, so you need two concurrent coroutines for this purpose.
However, if you always start and cancel both collections together, then I think it would be better to group them into a single coroutine like this:
fun beginSoundDensity() {
myJob?.cancel()
myJob = viewModelScope.launch {
coroutineScope {
launch {
aSoundMeter.startSoundDensity {
pauseSoundDensity()
}.cancellable()
.collect {
_soundDensityState.value = it
}
}
launch {
myFlow.collect {
_timeX.value = it //It will not be launched
}
}
}
}
}
fun resetSoundDensity(){
myJob?.cancel()
}

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 get a previous emission Kotlin Flow?

Let me use a simple image to illustrate what I want to get:
I don't want to use SharedFlow's replayCache to achieve this because if a new observer observes that SharedFlow, it will get 2 emissions instead of one latest emission.
Or if I write it in code:
val sharedFlow = MutableSharedFlow(replay = 1)
val theFlowThatIWant = sharedFlow.unknownOperator { … }
sharedFlow.emit(1)
sharedFlow.emit(2)
sharedFlow.collect {
println(it)
}
Expected output:
2
theFlowThatIWant.collect {
println(it)
}
Expected output:
1
We can create such operator by ourselves. We can generalize it to more items than only the last one and use circular buffer to keep postponed items:
suspend fun main() {
val f = flow {
repeat(5) {
println("Emitting $it")
emit(it)
delay(1000)
}
}
f.postponeLast()
.collect { println("Collecting $it") }
}
fun <T> Flow<T>.postponeLast(count: Int = 1): Flow<T> = flow {
val buffer = ArrayDeque<T>(count)
collect {
if (buffer.size == count) {
emit(buffer.removeFirst())
}
buffer.addLast(it)
}
}
Note that this solution never emits postponed items. If you like to emit them at the end, just add this after collect { }:
while (buffer.isNotEmpty()) {
emit(buffer.removeFirst())
}

How to simply add another source to MediatorLiveData in kotlin?

I want to combine multiple data sources in a MediatorLiveData. Unfortunately, there are not many examples yet. So in my ViewModel I have
//all lists have been declared before
val playerList = MediatorLiveData<List<Player>>()
init {
playerList.addSource(footballPlayerList) { value ->
playerList.value = value
}
playerList.addSource(basketballPlayerList) { value ->
playerList.value = value
}
}
But apparently this will always override the current value of playerList. I mean I could build some hacky workarounds with helper variables like _playerList but maybe there is an easier solution?
Having done quite some research.. I found it out. Here is an example
fun blogpostBoilerplateExample(newUser: String): LiveData<UserDataResult> {
val liveData1 = userOnlineDataSource.getOnlineTime(newUser)
val liveData2 = userCheckinsDataSource.getCheckins(newUser)
val result = MediatorLiveData<UserDataResult>()
result.addSource(liveData1) { value ->
result.value = combineLatestData(liveData1, liveData2)
}
result.addSource(liveData2) { value ->
result.value = combineLatestData(liveData1, liveData2)
}
return result
}
The actual combination of data is done in a separate combineLatestData method like so
private fun combineLatestData(
onlineTimeResult: LiveData<Long>,
checkinsResult: LiveData<CheckinsResult>
): UserDataResult {
val onlineTime = onlineTimeResult.value
val checkins = checkinsResult.value
// Don't send a success until we have both results
if (onlineTime == null || checkins == null) {
return UserDataLoading()
}
// TODO: Check for errors and return UserDataError if any.
return UserDataSuccess(timeOnline = onlineTime, checkins = checkins)
}
Here is a simple example
class MergeMultipleLiveData : ViewModel() {
private val fictionMenu: MutableLiveData<Resource<RssMenu>> = MutableLiveData()
private val nonFictionMenu: MutableLiveData<Resource<RssMenu>> = MutableLiveData()
val allCategoryMenus: MediatorLiveData<Resource<RssMenu>> = MediatorLiveData()
init {
getFictionMenus()
getNonFictionMenus()
getAllCategoryMenus()
}
private fun getAllCategoryMenus() = viewModelScope.launch(Dispatchers.IO) {
allCategoryMenus.addSource(fictionMenu) { value ->
allCategoryMenus.value = value
}
allCategoryMenus.addSource(nonFictionMenu) { value ->
allCategoryMenus.value = value
}
}
private fun getFictionMenus() = viewModelScope.launch(Dispatchers.IO) {
fictionMenu.postValue( // todo )
}
private fun getNonFictionMenus() = viewModelScope.launch(Dispatchers.IO) {
nonFictionMenu.postValue( // todo )
}
}
And in your fragment you can observer as;
viewModel.allCategoryMenus.observe(viewLifecycleOwner) {
// todo
}