Cahining coroutines by using extension functions in Kotlin - kotlin

I want to chain 3 coroutines by using Kotlin's extension functions. I know how to do it with regular ones, but can't manage it with extension functions. In fact, in the 2nd coroutine I can receive only one data sent from the 1st coroutine, but that's all. The program works but all I get on the console is Doc: 1st Document. What I'm doing wrong?
fun main(args: Array<String>) = runBlocking {
produceDocs().docLength().report().consumeEach {
println(it)
}
}
private fun CoroutineScope.produceDocs() = produce {
fun getDocs(): List<String> {
return listOf("1st Document", "2nd Newer Document")
}
while (this.isActive) {
val docs = getDocs()
for (doc in docs) {
send(doc)
}
delay(TimeUnit.SECONDS.toMillis(2))
}
}
private suspend fun ReceiveChannel<String>.docLength(): ReceiveChannel<Int> = coroutineScope {
val docsChannel: ReceiveChannel<String> = this#docLength
produce {
for (doc in docsChannel) {
println("Doc: $doc") // OK. This works.
send(doc.count()) // ??? Not sure where this sends data to?
}
}
}
private suspend fun ReceiveChannel<Int>.report(): ReceiveChannel<String> = coroutineScope {
val docLengthChannel: ReceiveChannel<Int> = this#report
produce {
for (len in docLengthChannel) {
println("Length: $len") // !!! Nothing arrived.
send("Report. Document contains $len characters.")
}
}
}

You have to consume each channel independently in order to make emissions go through the chain, otherwise the first emission will never be consumed:
private fun CoroutineScope.produceDocs() = produce {
fun getDocs(): List<String> {
return listOf("1st Document", "2nd Newer Document")
}
while (this.isActive) {
val docs = getDocs()
for (doc in docs) {
send(doc)
}
delay(TimeUnit.SECONDS.toMillis(2))
}
}
private suspend fun ReceiveChannel<String>.docLength() : ReceiveChannel<Int> = CoroutineScope(coroutineContext).produce {
for (doc in this#docLength) {
println("Doc: $doc") // OK. This works.
send(doc.count()) // ??? Not sure where this sends data to?
}
}
private suspend fun ReceiveChannel<Int>.report(): ReceiveChannel<String> = CoroutineScope(coroutineContext).produce {
for (len in this#report) {
println("Length: $len") // !!! Nothing arrived.
send("Report. Document contains $len characters.")
}
}
I suggest you a better approach to do the exact same thing using Flow:
private fun produceDocs(): Flow<String> = flow {
fun getDocs(): List<String> {
return listOf("1st Document", "2nd Newer Document")
}
while (true) {
val docs = getDocs()
for (doc in docs) {
emit(doc)
}
delay(TimeUnit.SECONDS.toMillis(2))
}
}
private fun Flow<String>.docLength(): Flow<Int> = flow {
collect { doc ->
println("Doc: $doc")
emit(doc.count())
}
}
private fun Flow<Int>.report(): Flow<String> = flow {
collect { len ->
println("Length: $len")
emit("Report. Document contains $len characters.")
}
}
Or better like this:
private fun produceDocs(): Flow<String> = flow {
fun getDocs(): List<String> {
return listOf("1st Document", "2nd Newer Document")
}
while (true) {
val docs = getDocs()
for (doc in docs) {
emit(doc)
}
delay(TimeUnit.SECONDS.toMillis(2))
}
}
private fun Flow<String>.docLength(): Flow<Int> = transform { doc ->
println("Doc: $doc")
emit(doc.count())
}
private fun Flow<Int>.report(): Flow<String> = transform { len ->
println("Length: $len")
emit("Report. Document contains $len characters.")
}
And collect it like this:
produceDocs().docLength().report().collect {
println(it)
}
Or even better like this:
produceDocs()
.map { doc ->
println("Doc: $doc")
doc.count()
}
.map { len ->
println("Length: $len")
"Report. Document contains $len characters."
}
.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

How to call handlePurchase() here? suspend function inside non-suspend

just wondering if its possible to call suspend fun handlePurchase() inside:
suspend fun handlePurchase(purchase: Purchase) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
val ackPurchaseResult = withContext(Dispatchers.IO) {
val client = BillingClient.newBuilder(this#GoproActivity).build()
client.acknowledgePurchase(acknowledgePurchaseParams.build())
}
}
}
}
private val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
handlePurchase(purchase) --->> here is the problem
}
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
}
}
inside onCreate:
var billingClient = BillingClient.newBuilder(this)
.setListener(purchasesUpdatedListener)
.enablePendingPurchases().build()
val skuList = ArrayList<String>()
skuList.add("dons.dogs.04")
button.setOnClickListener {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
// TODO("Not yet implemented")
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
val params = SkuDetailsParams.newBuilder()
params.setSkusList(skuList)
.setType(BillingClient.SkuType.INAPP)
billingClient.querySkuDetailsAsync(params.build()) { billingResult, skuDetailsList ->
for (skuDetails in skuDetailsList!!) {
val flowPurchase = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build()
val responseCode = billingClient.launchBillingFlow(
this#GoproActivity,
flowPurchase
).responseCode
}
}
}
}
override fun onBillingServiceDisconnected() {
TODO("Not yet implemented")
}
})
}
Everything should be Ok after that. Until now purchases are happening but canceled/refunded because are not acknowledged. I tried to follow different tutorials and resources also the official documentation but dont understund how to implement this part here. As i said the "purchasing" part works properly but can not acknowledge them. Is there any easier way to do that?
Thanks in advance.
You can use,
lifeCycleScope.launch{
//your suspend function here
}

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.

Observable from merged Observable.just and Subject emits nothing

i have a chain of calls from a presenter to repository which returns an observable. This is the code:
Presenter:
private fun getCategories() =
compositeDisposable.add(
categoriesUseCase.getCategories()
.timeout(TIMEOUT, TIMEOUT_UNIT)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handleCategories, this::handleCategoriesTimeout)
)
This is the usecase:
fun getCategories(): Observable<List<Category>> =
repository.getCategories()
.map { it.map { Category(it.id, it.text, it.icon) } }
This is the repo: //subject is BehaviorSubject.create()
fun getcategories(): Observable<List<DiscoverabilityCategoryElement>> =
Observable.just(storage.getCategories())
.mergeWith { subject.flatMapIterable { it.categories }.publish() }
.subscribeOn(Schedulers.io())
.doOnNext { Logger.d("Data", "next categories $it") }
.filter { it.isPresent }
.map { it.get() }
.take(1)
.doOnSubscribe { Logger.d("Data", "Subcribed categories") }
fun saveApiResult(response: Response) {//This is being called after subscribe
subject.onNext(response.categories)
subject.onComplete()
}
Method on storage will always return Optional.empty() (Meanwhile i'm developing)
My problem is, even seeing that subject.onNext is being called, that value never comes to the presenter, i've debug a bit and subject always returns false to hasObservables, maybe i'm losing my observer in some point?
Why do you call publish() on that line? It returns a ConnectableObservable which does nothing until connect is called. However, there is nothing on that line that would require sharing.
Try this:
fun getcategories(): Observable<List<DiscoverabilityCategoryElement>> =
Observable.just(storage.getCategories())
.mergeWith { subject.flatMapIterable { it.categories } } // <-------------
.subscribeOn(Schedulers.io())
.doOnNext { Logger.d("Data", "next categories $it") }
.filter { it.isPresent }
.map { it.get() }
.take(1)
.doOnSubscribe { Logger.d("Data", "Subcribed categories") }
Solution was change
.mergeWith { subject.flatMapIterable { it.categories }.publish() }
by
.mergeWith(subject.flatMap({ rootElement -> Observable.fromArray(element.categories.toOptional()) }))