Read value from Kotlin flow and return immediately - kotlin

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

Related

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

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 create a flow with a few subscribtions in Kotlin?

I need to run a task, which emits some data. I want to subscribe to this data like PublishSubject. But I can't solve a problem of one-instance flow. If I try to call it again, it will create another instance and the job will be done twice.
I tried to run the flow internally and post values to the BroadcastChannel, but this solution doesn't seem correct.
What is the best practice for such a task?
This will do the magic:
fun <T> Flow<T>.refCount(capacity: Int = Channel.CONFLATED, dispatcher: CoroutineDispatcher = Dispatchers.Default): Flow<T> {
class Context(var counter: Int) {
lateinit var job: Job
lateinit var channel: BroadcastChannel<T>
}
val context = Context(0)
fun lock() = synchronized(context) {
if (++context.counter > 1) {
return#synchronized
}
context.channel = BroadcastChannel(capacity)
context.job = GlobalScope.async(dispatcher) {
try {
collect { context.channel.offer(it) }
} catch (e: Exception) {
context.channel.close(e)
}
}
}
fun unlock() = synchronized(context) {
if (--context.counter == 0) {
context.job.cancel()
}
}
return flow {
lock()
try {
emitAll(context.channel.openSubscription())
} finally {
unlock()
}
}
}

Convert Observable of one type to another

I have the following code
class CurrencyRepository #Inject constructor(val apiInterface: ApiInterface,
val ratesDao: RatesDao) {
fun getRates(): Observable<List<Rates>> {
val observableFromApi = getCurrencyFromApi()
val observableFromDb = getRatesFromDb()
return Observable.concatArrayEager(observableFromApi , observableFromDb)
}
private fun getCurrencyFromApi(): Observable<Currency> {
return apiInterface.getRates()
.doOnNext {
Timber.i(it.toString())
val map = it.rates
val keys = map.keys
for (key in keys) {
ratesDao.insertRate(Rates(key , map.get(key)))
}
}
}
private fun getRatesFromDb(): Observable<List<Rates>> {
return ratesDao.getAllRates()
.toObservable()
.doOnNext {
for (rate in it) {
Timber.i("Repository DB ${it.size}")
}
}
}
}
In getCurrencyFromApi(), getRates() returns me an Observable<Currency>. I would like this particular function to return Observable<List<Rates>> so that I can use it in Observable.concatArrayEager inside getRates() of CurrencyRepository
Currency contains a Map object which can be transformed into a List object. I am not clear on how to do that inside getCurrencyFromApi()
One of possible solutions is
fun getRatesFromApi(): Observable<List<Rates>> {
return apiInterface.getRates()
.flatMapIterable { it.rates.entries }
.map { Rates(it.key ,it.value) }
.doOnNext { ratesDao.insertRate(it) }
.toList()
.toObservable()
}
I advise you insert items in database in one batch, because it will be more efficient.

How to write rx concatArrayEager equivalent in Kotlin CoRoutine?

I would like to convert my rxJava Code to Kotlin CoRoutine.
Below is the code makes both the api and db call and returns the data to UI whatever comes first. Let us say if DB response happens to be quicker than the api. In that case still, the api response would continue until it receives the data to sync with db though it could have done the UI update earlier.
How Would I do it?
class MoviesRepository #Inject constructor(val apiInterface: ApiInterface,
val MoviesDao: MoviesDao) {
fun getMovies(): Observable<List<Movie>> {
val observableFromApi = getMoviesFromApi()
val observableFromDb = getMoviesFromDb()
return Observable.concatArrayEager(observableFromApi, observableFromDb)
}
fun getMoviesFromApi(): Observable<List<Movie>> {
return apiInterface.getMovies()
.doOnNext { it ->
it.data?.let { it1 -> MoviesDao.insertAllMovies(it1) }
println("Size of Movies from API %d", it.data?.size)
}
.map({ r -> r.data })
}
fun getMoviesFromDb(): Observable<List<Movie>> {
return MoviesDao.queryMovies()
.toObservable()
.doOnNext {
//Print log it.size :)
}
}
}
As the first step you should create suspend funs for your ApiInterface and MovieDao calls. If they have some callback-based API, you can follow these official instructions.
You should now have
suspend fun ApiInterface.suspendGetMovies(): List<Movie>
and
suspend fun MoviesDao.suspendQueryMovies(): List<Movie>
Now you can write this code:
launch(UI) {
val fromNetwork = async(UI) { apiInterface.suspendGetMovies() }
val fromDb = async(UI) { MoviesDao.suspendQueryMovies() }
select<List<Movie>> {
fromNetwork.onAwait { it }
fromDb.onAwait { it }
}.also { movies ->
// act on the movies
}
}
The highlight is the select call which will simultaneously await on both Deferreds and act upon the one that gets completed first.
If you want to ensure you act upon the result from the network, you'll need some more code, for example:
val action = { movies: List<Movie> ->
// act on the returned movie list
}
var gotNetworkResult = false
select<List<Movie>> {
fromNetwork.onAwait { gotNetworkResult = true; it }
fromDb.onAwait { it }
}.also(action)
if (!gotNetworkResult) {
action(fromNetwork.await())
}
This code will act upon the DB results only if they come in before the network results, which it will process in all cases.
Something along those lines should work:
data class Result(val fromApi: ???, val fromDB: ???)
fun getMovies(): Result {
val apiRes = getMoviesFromApiAsync()
val dbRes = getMoviesFromDbAsync()
return Result(apiRes.await(), dbRes.await())
}
fun getMoviesFromApiAsync() = async {
return apiInterface.getMovies()
.doOnNext { it ->
it.data?.let { it1 -> MoviesDao.insertAllMovies(it1) }
println("Size of Movies from API %d", it.data?.size)
}
.map({ r -> r.data })
}
fun getMoviesFromDbAsync() = async {
return MoviesDao.queryMovies()
}
I don't know what you're returning, so I just put ??? instead.