obj in promoType = [list of string]
its more like 10 firebase queries are running here, looking in 10 particular set of nodes and going down further.
what i'm not sure, whether i require to put on async / await on each of the queries, but all i want is 10 of these queries to run and then result me in whether a couponKey is empty or not. All i want to do is to display whether a coupon entered was correct or not.
further, in changeUserType(couponKey, couponFoundAtKey), some database write operations occur.
fun checkPromo(promoCodeET: String) = async(UI) {
try {
val database = PersistentFirebaseUtil.getDatabase().reference
val job = async(CommonPool) {
for (obj in promoType) {
val query = database.child("promos").child(obj).orderByChild("promoCode").equalTo(promoCodeET)
query.addListenerForSingleValueEvent(object :
ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
if (dataSnapshot.exists()) {
couponKey = dataSnapshot.key.toString()
couponFoundAtKey = dataSnapshot.children.first().key.toString()
if (couponKey.isNotEmpty())
changeUserType(couponKey, couponFoundAtKey)
flag = true
}
}
override fun onCancelled(error: DatabaseError) {
// Failed to read value
}
})
if (flag) break
}
}
job.await()
}
catch (e: Exception) {
}
finally {
if (couponKey.isEmpty()){
Toast.makeText(this#Coupon, "Invalid coupon", Toast.LENGTH_LONG).show()
}
flag = true
}
}
There are several things I find wrong with your code:
You have an outer async(UI) which doesn't make sense
Your inner async(CommonPool) doesn't make sense either, because your database call is already async
You use the antipattern where you immediately await after async, making it not really "async" (but see above, the whole thing is async with or without this)
Your fetching function has a side-effect of changing the user type
To transfer the results to the caller, you again use side-effects instead of the return value
Your code should be much simpler. You should declare a suspend fun whose return value is the pair (couponKey, coupon):
suspend fun fetchPromo(promoType: String, promoCodeET: String): Pair<String, String>? =
suspendCancellableCoroutine { cont ->
val database = PersistentFirebaseUtil.getDatabase().reference
val query = database.child("promos").child(promoType)
.orderByChild("promoCode").equalTo(promoCodeET)
query.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
cont.resume(
dataSnapshot
.takeIf { it.exists() }
?.let { snapshot ->
snapshot.key.toString()
.takeIf { it.isNotEmpty() }
?.let { key ->
Pair(key, snapshot.children.first().key.toString())
}
}
)
}
override fun onCancelled(error: DatabaseError?) {
if (error != null) {
cont.resumeWithException(MyException(error))
} else {
cont.cancel()
}
}
})
}
To call this function, use a launch(UI) at the call site. Change the user type once you get a non-null value:
launch(UI) {
var found = false
for (type in promoType) {
val (couponKey, coupon) = fetchPromo(type, "promo-code-et") ?: continue
found = true
withContext(CommonPool) {
changeUserType(couponKey, coupon)
}
break
}
if (!found) {
Toast.makeText(this#Coupon, "Invalid coupon", Toast.LENGTH_LONG).show()
}
}
You say that changeUserType performs some database operations, so I wrapped them in a withContext(CommonPool).
Note also that I extracted the loop over promo types outside the function. This will result in queries being performed sequentially, but you can just write different calling code to achieve parallel lookup:
var numDone = 0
var found = false
promoType.forEach { type ->
launch(UI) {
fetchPromo(type, "promo-code-et")
.also { numDone++ }
?.also { (couponKey, coupon) ->
found = true
launch(CommonPool) {
changeUserType(couponKey, coupon)
}
}
?: if (numDone == promoType.size && !found) {
Toast.makeText(this#Coupon, "Invalid coupon", Toast.LENGTH_LONG).show()
}
}
}
Related
I have a code in my repository which has to call two endpoints. I have used Flowable.zip() but it doesn't seem to return a value. The Call doesn't fail even if there is no network available.
fun fetchRateRemote(): Flowable<ResultWrapper<List<RateModel>>> {
return Flowable.zip<Flowable<CurrenciesDTO>, Flowable<RateDTO>, ResultWrapper<List<RateModel>>>(
{
apiEndpoints.fetchCurrencies(key)
}, {
apiEndpoints.fetchRate(key)
}, { t1, t2 ->
val rateList = mutableListOf<RateModel>()
t2.subscribe { rate->
for((k,v) in rate.quotes ){
val currency = k.removeRange(0,3)
t1.subscribe {cur->
val currencyName = cur.currencies[currency]
if (currencyName != null) {
rateList.add(RateModel("$currencyName ($currency)", v.toString()))
}
}
}
}
ResultWrapper.Success(rateList)
}).subscribeOn(Schedulers.io())
}
I use a wrapper to mimic state and this is what I do in my viewmodel.
private fun fetchRates(){
disposable.add(repository.fetchRateRemote()
.startWith(ResultWrapper.Loading)
.onErrorReturn {
ResultWrapper.Error(it)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSubscriber<ResultWrapper<List<RateModel>>>() {
override fun onComplete() {}
override fun onNext(rate: ResultWrapper<List<RateModel>>) {
rates.postValue(rate)
}
override fun onError(error: Throwable) {
error.printStackTrace()
}
})
)
}
I then observe rate in my activity via LiveData. The wrapper or the observation isn't the issue. It works with other calls, I do not know why the zip call doesn't work. I'm fairly new to RxJava so If I didn't implement something correctly in my repository please help correct me.
Okay! I made a lot of mistakes with the code in the repository above but I managed to fix it. Here's the solution. The Type arguments for the zip method was wrong! I didn't call the BiFunction argument properly too.
fun fetchRateRemote(): Flowable<ResultWrapper<List<RateModel>>> {
return Flowable.zip<CurrenciesDTO, RateDTO, ResultWrapper<List<RateModel>>>(
apiEndpoints.fetchCurrencies(key), apiEndpoints.fetchRate(key), BiFunction { t1, t2 ->
val rateList = mutableListOf<RateModel>()
for((k,v) in t2.quotes ){
val currencyCode = k.removeRange(0,3)
val currencyName = t1.currencies[currencyCode]
if (currencyName != null) {
rateList.add(RateModel("$currencyName ($currencyCode)", v.toString()))
}
}
ResultWrapper.Success(rateList)
}).subscribeOn(Schedulers.io())
}
I'd like to verify two things:
if the way I use retryWhen is correct or not. Basically, when an exception is caught, I want to get an updated value and rerun the sequence.
if another function also needs value, how to make it waits for updateValue to complete in the first instance? I've played with .share() and RxReplayingShare but I'm not sure how to use those properly.
val value = 0
#Test
fun test() {
executeFunction()
.retryWhen { errors -> errors.flatMap { error ->
if (error is WrongValue) {
updateValue()
.doOnSuccess { value = it }
.toObservable()
} else {
Observable.error(error)
}
}
}
}
fun executeFunction(): Single<Int> =
if (value == 0) {
Single.error(WrongValue())
} else {
Single.just(value)
}
fun updateValue(): Single<Int> = Single.just(1)
I'm new to RxJava and after a few days of trying everything I could find online I see that I really need help with this one.
I fetch a member in my repository with local and remote sources. I added some operators to return my remote source in priority (via debounce), and to filter out errors so it would return only 1 of the 2 if either remote is not available or the database is empty.
It works fine as long as something is returned by one of my 2 sources, but the problem occurs if both sources returns errors: as I filter out the errors, it doesn't return anything, and my subscribe is never called.
Maybe there is a simple solution but I have not found it so far, could someone help?
Here is my fetchMember() in my Repository:
override fun fetchMember(): Observable<MemberModel?> {
return Observable.concatArrayDelayError(memberLocalSource.fetchMember(), memberRemoteSource.fetchMember())
.doOnNext { member ->
saveMember(member!!)
}
.materialize()
.filter { !it.isOnError }
.dematerialize { it -> it }
.debounce(400, TimeUnit.MILLISECONDS)
}
And here is my viewmodel:
fun fetchToken(username: String, password: String) {
val loginDisposable = authApiService.loginWithJWT(username, password)
.flatMap {
isAuthenticated = isTokenValid(username, password, it)
sharedPreferences.setHasValidCredentials(isAuthenticated)
memberRepository.fetchMember()
}
.subscribeOn(Schedulers.io())
.observeOn((AndroidSchedulers.mainThread()))
.doOnError { throwable ->
throwable.printStackTrace()
}
.subscribe(
{ member ->
memberLiveData.value = member
this.memberId = member!!.id.toString()
this.memberName = member.name.split(" ")[0]
if(isAuthenticated) {
authenticationState.value = AuthenticationState.AUTHENTICATED_VALID_MEMBER
} else {
authenticationState.value = AuthenticationState.UNAUTHENTICATED_VALID_MEMBER
}
},
{ error ->
if(isAuthenticated) {
authenticationState.value = AuthenticationState.AUTHENTICATED_INVALID_MEMBER
} else {
authenticationState.value = AuthenticationState.INVALID_AUTHENTICATION
}
})
disposable.add(loginDisposable)
}
private fun isTokenValid(username: String, password: String, authResponse: AuthModel): Boolean {
return if (authResponse.data != null) {
false
} else {
tokenInterceptor.token = authResponse.token
val tokenWithCredentials = AuthModel(authResponse.token, null, null, username, password)
tokenRepository.saveToken(tokenWithCredentials)
true
}
}
In the end I managed to make it work by adding:
.defaultIfEmpty(MemberModel(-1))
and checking against id == -1.
I want to be able to listen to realtime updates in Firebase DB's using Kotlin coroutines in my ViewModel.
The problem is that whenever a new message is created in the collection my application freezes and won't recover from this state. I need to kill it and restart app.
For the first time it passes and I can see the previous messages on the UI. This problem happens when SnapshotListener is called for 2nd time.
My observer() function
val channel = Channel<List<MessageEntity>>()
firestore.collection(path).addSnapshotListener { data, error ->
if (error != null) {
channel.close(error)
} else {
if (data != null) {
val messages = data.toObjects(MessageEntity::class.java)
//till this point it gets executed^^^^
channel.sendBlocking(messages)
} else {
channel.close(CancellationException("No data received"))
}
}
}
return channel
That's how I want to observe messages
launch(Dispatchers.IO) {
val newMessages =
messageRepository
.observer()
.receive()
}
}
After I replacing sendBlocking() with send() I am still not getting any new messages in the channel. SnapshotListener side is executed
//channel.sendBlocking(messages) was replaced by code bellow
scope.launch(Dispatchers.IO) {
channel.send(messages)
}
//scope is my viewModel
How to observe messages in firestore/realtime-dbs using Kotlin coroutines?
I have these extension functions, so I can simply get back results from the query as a Flow.
Flow is a Kotlin coroutine construct perfect for this purposes.
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/
#ExperimentalCoroutinesApi
fun CollectionReference.getQuerySnapshotFlow(): Flow<QuerySnapshot?> {
return callbackFlow {
val listenerRegistration =
addSnapshotListener { querySnapshot, firebaseFirestoreException ->
if (firebaseFirestoreException != null) {
cancel(
message = "error fetching collection data at path - $path",
cause = firebaseFirestoreException
)
return#addSnapshotListener
}
offer(querySnapshot)
}
awaitClose {
Timber.d("cancelling the listener on collection at path - $path")
listenerRegistration.remove()
}
}
}
#ExperimentalCoroutinesApi
fun <T> CollectionReference.getDataFlow(mapper: (QuerySnapshot?) -> T): Flow<T> {
return getQuerySnapshotFlow()
.map {
return#map mapper(it)
}
}
The following is an example of how to use the above functions.
#ExperimentalCoroutinesApi
fun getShoppingListItemsFlow(): Flow<List<ShoppingListItem>> {
return FirebaseFirestore.getInstance()
.collection("$COLLECTION_SHOPPING_LIST")
.getDataFlow { querySnapshot ->
querySnapshot?.documents?.map {
getShoppingListItemFromSnapshot(it)
} ?: listOf()
}
}
// Parses the document snapshot to the desired object
fun getShoppingListItemFromSnapshot(documentSnapshot: DocumentSnapshot) : ShoppingListItem {
return documentSnapshot.toObject(ShoppingListItem::class.java)!!
}
And in your ViewModel class, (or your Fragment) make sure you call this from the right scope, so the listener gets removed appropriately when the user moves away from the screen.
viewModelScope.launch {
getShoppingListItemsFlow().collect{
// Show on the view.
}
}
What I ended up with is I used Flow which is part of coroutines 1.2.0-alpha-2
return flowViaChannel { channel ->
firestore.collection(path).addSnapshotListener { data, error ->
if (error != null) {
channel.close(error)
} else {
if (data != null) {
val messages = data.toObjects(MessageEntity::class.java)
channel.sendBlocking(messages)
} else {
channel.close(CancellationException("No data received"))
}
}
}
channel.invokeOnClose {
it?.printStackTrace()
}
}
And that's how I observe it in my ViewModel
launch {
messageRepository.observe().collect {
//process
}
}
more on topic https://medium.com/#elizarov/cold-flows-hot-channels-d74769805f9
Extension function to remove callbacks
For Firebase's Firestore database there are two types of calls.
One time requests - addOnCompleteListener
Realtime updates - addSnapshotListener
One time requests
For one time requests there is an await extension function provided by the library org.jetbrains.kotlinx:kotlinx-coroutines-play-services:X.X.X. The function returns results from addOnCompleteListener.
For the latest version, see the Maven Repository, kotlinx-coroutines-play-services.
Resources
Using Firebase on Android with Kotlin Coroutines by Joe Birch
Using Kotlin Extension Functions and Coroutines with Firebase by Rosário Pereira Fernandes
Realtime updates
The extension function awaitRealtime has checks including verifying the state of the continuation in order to see whether it is in isActive state. This is important because the function is called when the user's main feed of content is updated either by a lifecycle event, refreshing the feed manually, or removing content from their feed. Without this check there will be a crash.
ExtenstionFuction.kt
data class QueryResponse(val packet: QuerySnapshot?, val error: FirebaseFirestoreException?)
suspend fun Query.awaitRealtime() = suspendCancellableCoroutine<QueryResponse> { continuation ->
addSnapshotListener({ value, error ->
if (error == null && continuation.isActive)
continuation.resume(QueryResponse(value, null))
else if (error != null && continuation.isActive)
continuation.resume(QueryResponse(null, error))
})
}
In order to handle errors the try/catch pattern is used.
Repository.kt
object ContentRepository {
fun getMainFeedList(isRealtime: Boolean, timeframe: Timestamp) = flow<Lce<PagedListResult>> {
emit(Loading())
val labeledSet = HashSet<String>()
val user = usersDocument.collection(getInstance().currentUser!!.uid)
syncLabeledContent(user, timeframe, labeledSet, SAVE_COLLECTION, this)
getLoggedInNonRealtimeContent(timeframe, labeledSet, this)
}
// Realtime updates with 'awaitRealtime' used
private suspend fun syncLabeledContent(user: CollectionReference, timeframe: Timestamp,
labeledSet: HashSet<String>, collection: String,
lce: FlowCollector<Lce<PagedListResult>>) {
val response = user.document(COLLECTIONS_DOCUMENT)
.collection(collection)
.orderBy(TIMESTAMP, DESCENDING)
.whereGreaterThanOrEqualTo(TIMESTAMP, timeframe)
.awaitRealtime()
if (response.error == null) {
val contentList = response.packet?.documentChanges?.map { doc ->
doc.document.toObject(Content::class.java).also { content ->
labeledSet.add(content.id)
}
}
database.contentDao().insertContentList(contentList)
} else lce.emit(Error(PagedListResult(null,
"Error retrieving user save_collection: ${response.error?.localizedMessage}")))
}
// One time updates with 'await' used
private suspend fun getLoggedInNonRealtimeContent(timeframe: Timestamp,
labeledSet: HashSet<String>,
lce: FlowCollector<Lce<PagedListResult>>) =
try {
database.contentDao().insertContentList(
contentEnCollection.orderBy(TIMESTAMP, DESCENDING)
.whereGreaterThanOrEqualTo(TIMESTAMP, timeframe).get().await()
.documentChanges
?.map { change -> change.document.toObject(Content::class.java) }
?.filter { content -> !labeledSet.contains(content.id) })
lce.emit(Lce.Content(PagedListResult(queryMainContentList(timeframe), "")))
} catch (error: FirebaseFirestoreException) {
lce.emit(Error(PagedListResult(
null,
CONTENT_LOGGED_IN_NON_REALTIME_ERROR + "${error.localizedMessage}")))
}
}
This is working for me:
suspend fun DocumentReference.observe(block: suspend (getNextSnapshot: suspend ()->DocumentSnapshot?)->Unit) {
val channel = Channel<Pair<DocumentSnapshot?, FirebaseFirestoreException?>>(Channel.UNLIMITED)
val listenerRegistration = this.addSnapshotListener { value, error ->
channel.sendBlocking(Pair(value, error))
}
try {
block {
val (value, error) = channel.receive()
if (error != null) {
throw error
}
value
}
}
finally {
channel.close()
listenerRegistration.remove()
}
}
Then you can use it like:
docRef.observe { getNextSnapshot ->
while (true) {
val value = getNextSnapshot() ?: continue
// do whatever you like with the database snapshot
}
}
If the observer block throws an error, or the block finishes, or your coroutine is cancelled, the listener is removed automatically.
Is there anything similar in Kotlin that provides same ability as the Swift keyword 'defer' ?
What the defer key word does is, it ensure that the code inside a defer block get executed before returning from a function.
Below is an example imagining that defer keyword existed in Kotlin.
class MyClass {
var timeStamp = 0L
fun isEdible(fruit: Fruit): Boolean {
defer {
timeStamp = System.currentTimeMillis()
}
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
In the case above, regardless of at what point the function returns, the block inside defer will get executed and timestamp's value will get updated, just before the function ends.
I know Java there is the finally {} keyword used along with try{} catch{}, but it's is not exactly what defer offers.
There's no such keyword in Kotlin, but you can make a construct yourself that will work quite similarly. Something like this (note that this does not handle exceptions in the deferred blocks):
class Deferrable {
private val actions: MutableList<() -> Unit> = mutableListOf()
fun defer(f: () -> Unit) {
actions.add(f)
}
fun execute() {
actions.forEach { it() }
}
}
fun <T> defer(f: (Deferrable) -> T): T {
val deferrable = Deferrable()
try {
return f(deferrable)
} finally {
deferrable.execute()
}
}
Then you can use it like this:
class MyClass {
var timeStamp = 0L
fun isEdible(fruit: Fruit): Boolean = defer { d ->
d.defer {
timeStamp = System.currentTimeMillis()
}
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
The closest equivalent is try/finally. catch is not necessary if there's no exceptions thrown.
try {
println("do something")
// ... the rest of your method body here
}
finally {
println("Don't forget about me!");
}
In Swift, defer is usually used to ensure you don't forget to clean up some kind of resource or another (file handle, database connection, shared memory map, etc.). For this purpose, Kotlin use with, which takes a closure, to which the resource is passed as an argument. The resource is valid for the lifetime of the closure, and is automatically closed at the end.
FileWriter("test.txt")
.use { it.write("something") }
// File is closed by now
Solution with exception handling:
class DeferContext {
private val list = mutableListOf<() -> Unit>()
fun defer(payload: () -> Unit) {
list += payload
}
/** lombok `#Cleanup` analog */
fun AutoCloseable.deferClose() = apply {
defer { close() }
}
fun executeDeferred(blockError: Throwable?) {
var error: Throwable? = blockError
for (element in list.reversed()) {
try {
element()
} catch (e: Throwable) {
if (error == null) {
error = e
} else {
error.addSuppressed(e)
}
}
}
error?.let { throw it }
}
}
inline fun <T> deferBlock(payload: DeferContext.() -> T): T {
val context = DeferContext()
var error: Throwable? = null
var result: T? = null
try {
result = context.payload()
} catch (e: Throwable) {
error = e
} finally {
context.executeDeferred(error)
}
return result as T
}
IMHO, main point of defer functionality is execution of deferred actions regardless of previously thrown exceptions.
usage:
deferBlock {
defer { println("block exited") }
val stream = FileInputStream("/tmp/a").deferClose()
}
I came across the same question today.
While I think the answer provided by marstran is good, I decided to refactor it a little bit.
fun <T> deferred(f: ((() -> Unit) -> Unit) -> T): T {
val actions: MutableList<() -> Unit> = mutableListOf()
try {
return f(actions::add)
} finally {
actions.asReversed().forEach { it() }
}
}
I got rid of the Deferrable class by using the list directly in the deffered function. This also solves the fact that the whole Deferrable object was passed to the calling code needing to call it.defer/d.defer. In this version the add method of the mutable list is directly passed into the lambda allowing to have a code that is closer to its go/swift version.
To address the suggestion given by mvndaai to use Stack I decided to call .asReversed() on the list. Maybe there is a LI-FO type in kotlin that is also available in non JVM variants, but if not I think this is a good solution.
the given sample would look like:
class MyClass {
var timeStamp = 0L
fun isEdible(fruit: Fruit): Boolean = deferred { defer ->
defer {
timeStamp = System.currentTimeMillis()
}
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
If the class is Closeable you can use use block:
class MyClass : Closeable {
var timeStamp = 0L
override fun close() {
timeStamp = System.currentTimeMillis()
}
fun test(): Boolean {
this.use {
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
}