I'm doing some tests and I cannot understand the different behaviour when I use the GlobalScope.launch and viewModelScope CoroutineScope
I have the following code in a viewModel:
Log.d("init")
GlobalScope.launch((Dispatchers.Main)) {
repository
.storedItemsListener()
.onStart {
Log.d("onStart")
}
.onEach {
Log.d("onEach")
}
.onEmpty {
Log.d("onEmpty")
}
.onCompletion {
Log.d("onCompletion")
}
.launchIn(this)
}
Whenever the database is updated with items, storedItemsListener logs Storage Updated
At app launch, I perform a network request that updates storage with items. I then do a pull to refresh that performs a new request that also stores items on the database. With the above code, I have these logs:
// After app launch
init
onStart
Storage Updated //when the listener on the database is triggered
onEach
//After pull to refresh, although I know I store items on the database, no logs are produced. It seems that is the coroutine scope is dead and stops responding.
I then change the above code to use viewModel (.launchIn(viewModelScope)). I then obtain the logs that I expect.
// After app launch
init
onStart
Storage Updated //when the listener on the database is triggered
onEach
Storage Updated //when storage is updated with network request result
onEach
//After pull to refresh
Storage Updated //when storage is updated with network request result
onEach
My question is this. Shouldn't GlobalScope.launch be kept “alive” and notify me of all storage updates?
Please note that I want to keep this mechanism alive always and not only bound to a viewModel scope, and this is why I've chosen Global scope. The above description is a simplified version of what I need.
Related
I'm having a little issue with FirebaseAuth.AuthStateListener while working with email verification on Firebase. I've verified my email by clicking the received verification link, and then I reloaded the current user by the lines of code below:
suspend fun reloadUserInfo() {
firebaseAuth.currentUser?.reload()?.await()
}
But AuthStateListener is not firing up even tho I reloaded the cached user. If I understood correctly AuthStateListener should trigger after reloading the current user. The reload() function's documentation says: Manually refreshes the data of the current user (for example, attached providers, display name, and so on). The isEmailVerified state changed the firebase user. Right?
val isEmailVerified: Flow<Boolean> = callbackFlow {
val authStateListener = AuthStateListener { auth ->
val isEmailVerified = auth.currentUser?.isEmailVerified == true
trySend(isEmailVerified)
}
firebaseAuth.addAuthStateListener(authStateListener)
awaitClose {
firebaseAuth.removeAuthStateListener(authStateListener)
}
}
This flow is not sending anything. But after restarting the application the callback gets fired. I don't want to restart the application to get the job done. It would not be a good user experience.
I did some research but nothing was found. If you take the time to help me, I appreciate it.
The email verification happens out of the band, so when you click the link in the email, there is nothing built-in that triggers Firebase Authentication clients to be updated. So there is no callback for that. Firebase only refreshes the ID token once per hour. This means that it may take up to an hour before the token is refreshed, a case in which the onIdTokenChanged() method fires. So onAuthStateChanged() nor onIdTokenChanged() fires when the link is clicked, which basically means that we have to check that in our application code, on demand. Since you're using Kotlin, the solution is quite simple:
return try {
auth.currentUser?.reload()?.await()
} catch (e: Exception) {
Log.e(TAG, "${e.message}")
}
However, do not attach a complete listener and call await() at the same time. It's one or the other. Do not combine them! Why? Because there is no guarantee that the listener will be called before or after await(). That means that there is a chance that the code in the complete listener won't be called until or after the suspend function returns. Besides that, one of the major reasons to use Kotlin Coroutines in the first place is to avoid using callbacks, which are not life-cycle aware.
If you want to see a concrete example, this resource will help. Here is the corresponding repo.
I'm having a tough time figuring out why do I observe InterruptedExceptions in the SyncAdapter#onPerformSync method.
Brief intro
My project originally contained only Java code and had a working SyncAdapter pattern implemented.
In the onPerfromSync method I did the following operations for each resource that I needed to sync:
query local sqlite database
send data to web server over HTTP
save data to local sqlite database
As the project progressed I introduced Kotlin and Coroutines and decided to use them in the SyncAdapter.
Most of the resources kept the existing logic, and for a few I decided to use Coroutines.
I had to bridge the onPerformSync method with a CoroutineScope so that I can launch coroutines. After doing so I started observing InterruptedExceptions occurring.
Code before (no exceptions)
class MySyncAdapter extends AbstractThreadedSyncAdapter {
...
#Override
public void onPerformSync(...) {
syncResourceX();
syncResourceY();
}
private void syncResourceX() {
// query db
// send to server
// store locally
}
}
Code After (with exceptions)
class MySyncAdapter(...): AbstractThreadedSyncAdapter(...) {
...
override fun onPerformSync(...) {
runBlocking {
syncResourceX();
syncResourceY();
}
}
private suspend fun syncResourceX() {
// query db
// send to server
// store locally
}
}
After this refactoring I started receiving InterruptedExceptions where runBlocking is being invoked.
Initially I thought this might be due to not performing network operations on the current thread as the docs of the sync adapter state that the system monitors network traffic and might interrupt the sync process if no traffic is generated.
But runBlocking should cause any network requests to be executed on the current thread right?
After which I started thinking that this process has existed in the Java code as well. It's just that there is nothing to report the interruption of the sync process. It is up until I started using coroutines and runBlocking that this problem has revealed it self. Or so I think now.
Question:
Any thoughts or explanations why I previously did not observe InterruptedException with the Java code and do observe InterruptedException with the Kotlin code is more than welcome.
Notes
I haven't overriden onSyncCanceled
The sync process usually takes less then 20 seconds to complete
I'm using LiveData+Room+ViewModel to build a simple kotlin app. The main activity (which presents a list) is getting the required data from a ViewModel which is getting the info from a database (the data is transformed before being consumed by the activity). Now, I need to allow the user to refresh the data through a swipe. When that happens, the app should check if the current connection can be used for that and if it can't, then the app should schedule a job.
Currently, I'm delegating this work (check the current connection and the eventual job scheduling) to my ViewModel. It looks like this:
fun tryToRefreshDataFromService(){
//first, check if there's network
//If there is, call web service and then update db
//if no network, schedule a job and try to refresh from the database
if(canGetDataFromNetwork()){
Timber.d("With network access, getting data from web services")
WebServiceAsyncTask(newsManager).execute()
}
else{
//schedule job for refreshing
//no network access, setting up job
Timber.d("No network access, setting up job")
scheduleJob()
}
}
The activity will then be able to call the method from within a helper method (which handles the swiper refresh event):
private fun recoverDataForTabs(swiper: SwipeRefreshLayout? = null){
_swiper = swiper //for clearing
_viewModel.tryToRefreshDataFromService()
}
However, it seems like this is really a bad idea because it seems like ViewModels shouldn't know anything about Android framework classes (and that ends up being required for this case). So, does this mean that I should update my code so that the network checking + job scheduling is done from the activity?
Thanks
You can inject framework-related objects into your ViewModels. For example:
class MyViewModel(val networkChecker: IMyNetworkChecker, val jobSetter: IMyJobSetter, ...) {
fun tryToRefreshDataFromService(){
if(networkChecker.canGetDataFromNetwork()){
Timber.d("With network access, getting data from web services")
WebServiceAsyncTask(newsManager).execute()
}
else{
Timber.d("No network access, setting up job")
jobSetter.scheduleJob()
}
}
}
I noticed this behavior:
Disconnect a device from the internet
Create a few new documents while still offline
Close the app Then
Go online and reopen App
Documents are automatically synced with firestore (I lose control over completeListener)
Problem is that actions I had in onCompleteListener are not run.
Like in this snippet (do some super important stuff) is not run in the described scenario, also OnFailureListener is not called when a user is offline, so I cannot tell if it went ok or not.
FirebaseFirestore.getInstance().document(...).collection(...)
.set(message).addOnCompleteListener {
//do some super important stuff
}
I would rather do sync in this case on my own so I want it to fail and not repeat again.
How can I disable automatic sync of firestore?, for this one case only
So I wanted to ask this question and somehow Firestore transactions got to me.
So to fix this problem I used transactions (firestore / realtime dbs)
Transactions will fail when the client is offline.
So how it works now.
I try to run the transaction.
A If it fails I store it into a database
B If it succeeds I try to remove it from the database
And on every app start, I run sync service (check unsynced dbs and insert missing)
val OBJECT = ...
val ref = FirebaseFirestore.getInstance().document(...).collection(...)
FirebaseFirestore.getInstance().runTransaction(
object : Transaction.Function<Boolean> {
override fun apply(transaction: Transaction): Boolean? {
transaction.set(ref, OBJECT)
transaction.update(ref, PROPERTY, VALUE)
return true
}
})
.addOnSuccessListener {
println("Inserting $OBJECT ended with success==$it")
//todo remove from dbs (unsynced messages)
}.addOnFailureListener {
it.printStackTrace()
//todo save to dbs (unsynced messages)
}
//They work similarly in realtime database
In my Android app I am using domain level Repository interface, which is backed with local DB implemented using SqlBrite and network api with Retrofit observables. So I have method getDomains(): Observable<List<Domain>> in Repository and two corresponding methods in my Retrofit and SqlBrite.
I don't want to concatenate or merge, or amb these two observables. I want my Repository to take data only from SqlBrite and since SqlBrite returns QueryObservable, which triggers onNext() every time underlying data changed, I can run my network request independently and store results to SqlBrite and have my Observable updated with fetched from network and stored to DB data.
So I tried to implement my Repository's getDomains() method as follow:
fun getDomains(): Observable<List<Domain>> {
return db.getDomains()
.doOnSubscribe {
networkClient.getDomains()
.doOnNext { db.putDomains(it) }
.onErrorReturn{ emptyList() }
.subscribe()
}
}
But in this case every time the client should subscribe, every time it would make network requests, that is not so good. I thought about other do... operators to move requests there, but doOnCompleted() in case of QueryObservable would never be called, until I call toBlocking() somewhere, which I won't, doOnEach() also not good as it makes requests every time item from db extracted.
I also tried to use replay() operator, but though the Observable cached in this case, the subscription happens and results in network requests.
So, how can combine these two Observables in the desired way?
Ok, it depends on the concrete use case you have: i.e. assuming you want to display the latest data from your local database and from time to time update the database by doing a network request in the background.
Maybe there is a better way, but maybe you could do something like this
fun <T> createDataAwareObservable(databaseQuery: Observable<T>): Observable<T> =
stateDeterminer.getState().flatMap {
when (it) {
State.UP_TO_DATE -> databaseQuery // Nothing to do, data is up to date so observable can be returned directly
State.NO_DATA ->
networkClient.getDomains() // no data so first do the network call
.flatMap { db.save(it) } // save network call result in database
.flatMap { databaseQuery } // continue with original observable
State.SYNC_IN_BACKGROUND -> {
// Execute sync in background
networkClient.getDomains()
.flatMap { db.save(it) }
.observeOn(backgroundSyncScheduler)
.subscribeOn(backgroundSyncScheduler)
.subscribe({}, { Timber.e(it, "Error when starting background sync") }, {})
// Continue with original observable in parallel, network call will then update database and thanks to sqlbrite databaseQuery will be update automatically
databaseQuery
}
}
}
So at the end you create your SQLBrite Observable (QueryObservable) and pass it into the createDataAwareObservable() function. Than it will ensure that it loads the data from network if no data is here, otherwise it will check if the data should be updated in background (will save it into database, which then will update the SQLBrite QueryObservable automatically) or if the data is up to date.
Basically you can use it like this:
createDataAwareObservable( db.getAllDomains() ).subscribe(...)
So for you as user of this createDataAwareObservable() you always get the same type Observable<T> back as you pass in as parameter. So essentially it seems that you were always subscribing to db.getAllDomains() ...
if your problem is that you have to subscribe your observer every time that you want to get data you can use relay, which never unsubscribe the observers because does not implement onComplete
/**
* Relay is just an observable which subscribe an observer, but it wont unsubscribe once emit the items. So the pipeline keep open
* It should return 1,2,3,4,5 for first observer and just 3, 4, 5 fot the second observer since default relay emit last emitted item,
* and all the next items passed to the pipeline.
*/
#Test
public void testRelay() throws InterruptedException {
BehaviorRelay<String> relay = BehaviorRelay.create("default");
relay.subscribe(result -> System.out.println("Observer1:" + result));
relay.call("1");
relay.call("2");
relay.call("3");
relay.subscribe(result -> System.out.println("Observer2:" + result));
relay.call("4");
relay.call("5");
}
Another examples here https://github.com/politrons/reactive/blob/master/src/test/java/rx/relay/Relay.java