RxJava2 + Room: data is not being inserted in DB after clearAllTables() call - android

In my Android application after successful login I'm saving session info in Room, then I'm retrieving user information from BE and saving it too.
Everything works fine. I can see saved information in database tables.
When user logs out from application all tables are being cleared with appDatabase.clearAllTables() method call.
The catch is that on subsequent login there's no information that is being inserted in DB but my Rx calls aren't throwing any errors.
I've tried to use logging and debug but everything looks like normal.
Logging shows following actions being performed: login -> get user info from BE -> save session -> save user.
When debugging I can see that user info is being handled in UserDao_Impl's insertUser() method.
In application I use RxJava2 version 2.2.2, Room persistence library version 1.1.1, dependencies are being provided with Dagger version 2.19.
Here's my code snippets:
LoginUserUseCase.java
public Completable execute(#NonNull LoginInfo loginInfo) {
Completable loginAndSaveSession = sessionRepository.loginUser(loginInfo)
.flatMapCompletable(
session -> sessionRepository.saveSession(
session));
Completable getAndSaveUserInfo = userRepository.getRemoteUserInfo()
.flatMapCompletable(
user -> userRepository.saveUser(
user));
return loginAndSaveSession.andThen(getAndSaveUserInfo);
}
UserRepository.java
public Completable saveUser(#NonNull User user) {
return Completable.fromAction(() -> userDao.insertUser(user));
}
UserDao.java
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insertUser(User user);
LogoutUserUseCase.java
public Completable execute() {
return Completable.fromAction(() -> appDatabase.clearAllTables());
}
UPDATE (ISSUE RESOLVED): wasted a day on this bug. Finally come to understanding that some other library can affect application's work with Room. Turned out that Android Debug Database library that I've used to peek inside the database on device has a bug that breaks DB after you opened it once with its help.
Returning back to Stetho.

Related

How to properly get the results from firestore database fetches embracing ascynchronous functionality? - Android - Kotlin - MVVM

Because database fetches usually happen asynchronously by default, a variable that holds the data from the firebase database fetch will be null when used right after the fetch. To solve this I have seen people use the ".await()" feature in Kotlin coroutines but this goes against the purpose of asynchronous database queries. People also call the succeeding code from within 'addOnSuccessListener{}' but this seems to go against the purpose of MVVM, since 'addOnSuccessListener{}' will be called in the model part of MVVM, and the succeeding code that uses the fetched data will be in the ViewModel. The answer I'm looking for is maybe a listener or observer that is activated when the variable (whose value is filled from the fetched data) is given a value.
Edit:
by "succeeding code" I mean what happens after the database fetch using the fetched data.
As #FrankvanPuffelen already mentioned in his comment, that's what the listener does. When the operation for reading the data completes the listener fires. That means you know if you got the data or the operation was rejected by the Firebase servers due to improper security rules.
To solve this I have seen people use the ".await()" feature in Kotlin coroutines but this goes against the purpose of asynchronous database queries.
It doesn't. Using ".await()" is indeed an asynchronous programming technique that can help us prevent our applications from blocking. When it comes to the MVVM architecture pattern, the operation for reading the data should be done in the repository class. Since reading the data is an asynchronous operation, we need to create a suspend function. Assuming that we want to read documents that exist in a collection called "products", the following function is needed:
suspend fun getProductsFirestore(): List<Product> {
var products = listOf<Product>()
try {
products = productsRef.get().await().documents.mapNotNull { snapShot ->
snapShot.toObject(Product::class.java)
}
} catch (e: Exception) {
Log.d("TAG", e.message!!)
}
return products
}
This method can be called from within the ViewModel class:
val productsLiveData = liveData(Dispatchers.IO) {
emit(repository.getProductsFromFirestore())
}
So it can be observed in activity/fragment class:
private fun getProducts() {
viewModel.producsLiveData.observe(this, {
print(it)
//Do what you need to do with the product list
})
}
I have even written an article in which I have explained four ways in which you can read the data from Cloud Firestore:
How to read data from Cloud Firestore using get()?

Is there any way to pass a Flow into a Room DAO Query method?

I'm working on implementing Room in an android app and I have a use case where I am receiving a Flow with the current user id for functionality similar to switching which logged in Google user to view a service as. I'd like to pass that flow into a #Query annotated method in my DAO to get user widgets so that if the currently selected user changes or the list of widgets stored changes, the output Flow<List> would change as well.
Something like
WidgetRepo.kt
val widgets: Flow<List<Widget>> = widgetDAO.getWidgetsByUser(currentUserID)
WidgetDao.kt
#Query("select * from widget where userID = :userID")
fun getWidgetsByUser(userID: Flow<Int>): Flow<List<Widget>>
I don't think that's a good practice. I would rather do the following logic in a specific use-case, for example:
class GetWidgetsByUserUseCase(
private val userRepo: UserRepository,
private val widgetLocalSource: WidgetRepository
){
suspend operator fun invoke() = userRepo.flatMapLatest { user ->
widgetLocalSource.getWidgetsByUser(user.id)
}
}
With this implementation every time a new user is emitted, the widgetLocalSource.getWidgetsByUser() will be triggered and the previous flows will be canceled.
See flatMapLatest for more information on the operator.
Room Database has supported Flow since before. Add this to gradle:
// Kotlin Extensions and Coroutines support for Room - latest Room version 2.3.0
implementation("androidx.room:room-ktx:$room_version")
See more Write observable queries with Room here.

How to check if Cloud Firestore data is cached? [duplicate]

After deleting data from my Firestore Database, it takes my Android app some time to realize that the data was deleted, and I assume that it's happening due the auto data cache. My app has nothing to do with offline usage and I'd like to disable this feature...
I have added this in my custom Application Class:
import android.app.Application;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.FirebaseFirestoreSettings;
public class ApplicationClass extends Application {
#Override
public void onCreate() {
super.onCreate();
FirebaseFirestore db=FirebaseFirestore.getInstance();
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(false)
.build();
db.setFirestoreSettings(settings);
}
}
The problem occurs after turning off the internet connection and than turning it back on (while the app is still running, in the background or not)- the Firestore module seems to lose connection to the server, and it makes the opposite operation than the intended one - instead of stop taking data from the cache, it takes data from the cache only.
For example, debugging this code will always show that isFromCache is true and documentSnapshot is empty (even though that on the server side - it's not empty):
usersRef.document(loggedEmail).collection("challenges_received").get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
boolean isFromCache=documentSnapshots.getMetadata().isFromCache();
if (!documentSnapshots.isEmpty()) {
}
}
});
Is this normal behavior?
Is there another way to disable the data cache in Cloud Firestore?
EDIT:
Adding: FirebaseFirestore.setLoggingEnabled(flase); (instead of the code above) in the custom Application Class gives the same result.
According to Cloud Firestore 16.0.0 SDK update, there is now a solution to this problem:
You are now able to choose if you would like to fetch your data from the server only, or from the cache only, like this (an example for server only):
DocumentReference documentReference= FirebaseFirestore.getInstance().document("example");
documentReference.get(Source.SERVER).addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
//...
}
});
For cache only, just change the code above to Source.CACHE.
By default, both methods still attempt server and fall back to the cache.
I just ran a few tests in an Android application to see how this works. Because Firestore is currently still in beta release and the product might suffer changes any time, i cannot guarantee that this behaviour will still hold in the future.
db.collection("tests").document("fOpCiqmUjAzjnZimjd5c").get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
DocumentSnapshot documentSnapshot = task.getResult();
System.out.println("isFromCache: " + documentSnapshot.getMetadata().isFromCache());
}
});
Regarding the code, is the same no matter if we're getting the data from the cache or you are connected to the servers.
When I'm online it prints:
isFromCache: false
When I'm offline, it prints:
isFromCache: true
So, for the moment, there is no way to stop the retrieval of the data from the cache while you are not connected to the server, as you cannot force the retrieval of the data from the cache while you're connected to the server.
If instead I use a listener:
db.collection("tests").document("fOpCiqmUjAzjnZimjd5c").addSnapshotListener(new DocumentListenOptions().includeMetadataChanges(), new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(DocumentSnapshot documentSnapshot, FirebaseFirestoreException e) {
System.out.println("listener.isFromCache: " + documentSnapshot.getMetadata().isFromCache());
}
});
I get two prints when I'm online:
listener.isFromCache: true
listener.isFromCache: false
Firestore is desinged to retrieve data from the chache when the device is permanently offline or while your application temporarily loses its network connection and for the moment you cannot change this behaviour.
As a concusion, an API that does something like this, currently doesn't exist yet.
Edit: Unlike in Firebase, where to enable the offline persistence you need use this line of code:
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
In Firestore, for Android and iOS, offline persistence is enabled by default.
Using the above line of code, means that you tell Firebase to create a local (internal) copy of your database so that your app can work even if it temporarily loses its network connection.
In Firestore we find the opposite, to disable persistence, we need to set the PersistenceEnabled option to false. This means that you tell Firestore not to create a local copy of your database on user device, which in term means that you'll not be able to query your database unless your are connected to Firebase servers. So without having a local copy of your database and if beeing disconected, an Exception will be thrown. That's why is a good practice to use the OnFailureListener.
Update (2018-06-13): As also #TalBarda mentioned in his answer this is now possible starting with the 16.0.0 SDK version update. So we can achieve this with the help of the DocumentReference.get(Source source) and Query.get(Source source) methods.
By default, get() attempts to provide up-to-date data when possible by waiting for data from the server, but it may return cached data or fail if you are offline and the server cannot be reached. This behavior can be altered via the Source parameter.
So we can now pass as an argument to the DocumentReference or to the Query the source so we can force the retrieval of data from the server only, chache only or attempt server and fall back to the cache.
So something like this is now possible:
FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference docIdRef = db.collection("tests").document("fOpCiqmUjAzjnZimjd5c");
docIdRef.get(Source.SERVER).addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
//Get data from the documentSnapshot object
}
});
In this case, we force the data to be retrieved from the server only. If you want to force the data to be retrieved from the cache only, you should pass as an argument to the get() method, Source.CACHE. More informations here.
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(false)
.build();
dbEventHome.setFirestoreSettings(settings);
By setting this it is fetching from server always.
In Kotlin:
val db:FirebaseFirestore = Firebase.firestore
val settings = firestoreSettings {
isPersistenceEnabled = false
}
db.firestoreSettings = settings
// Enable Firestore logging
FirebaseFirestore.setLoggingEnabled(flase);
// Firestore
mFirestore = FirebaseFirestore.getInstance();
In general: the Firebase client tries to minimize the number of times it downloads data. But it also tries to minimize the amount of memory/disk space it uses.
The exact behavior depends on many things, such as whether the another listener has remained active on that location and whether you're using disk persistence. If you have two listeners for the same (or overlapping) data, updates will only be downloaded once. But if you remove the last listener for a location, the data for that location is removed from the (memory and/or disk) cache.
Without seeing a complete piece of code, it's hard to tell what will happen in your case.
Alternatively: you can check for yourself by enabling Firebase's logging [Firebase setLoggingEnabled:YES];
try this For FireBase DataBase
mDatabase.getReference().keepSynced(false);
FirebaseDatabase.getInstance().setPersistenceEnabled(false);
In Kotlin;
val settings = FirebaseFirestoreSettings.Builder()
with(settings){
isPersistenceEnabled = false
}
Firebase.firestore.firestoreSettings = settings.build()

Firestore how to disable automatic sync with cloud database?

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

why method with Flowable<List> of Room DAO never completes?

Fetching data from DB , Room DAO has a method that returns a Flowable userDao.getInfo(), this Flowable will never completes, I tested adding doOnNext() it emits 5 times (DB contains 5 items) but complete is never called, but I need as I have toList(),what could be the alternative for this
return userDatas()
.flatMapIterable(items -> items)
.flatMap(userData -> userDao.getInfo(userData.getId())
.map(user -> user.toStoreModel(...)//added doOnNext()-works 5 times and doOnComplete()doesn't work
.doOnNext(userData -> Log.i("test",""+userData))
.doOnComplete(() -> Log.i("test","complete"))
.toList()
.map(UserModel::fromUserModels)
.toFlowable();
#Query("SELECT * FROM user WHERE id = :id")
Flowable<...> getInfo(Long Id);
public Flowable<List<UserStore>> userDatas() {
return userDao.allUserDatas()
.take(1)//added complete and next works
.filter(userDatas -> !userDatas.isEmpty())
.switchIfEmpty(userIds()
.doOnNext(userDatas -> userDao.insert(userDatas)));
}
I have tested and even when I'm replacing userDatas() only with userDao.allUserDatas() (I'm sure it exists in DB) it gives the same results
If you need to have called complete method you can use take(1).but in that case you could not listener further DB changes
Everything is ok with your code ,it would never complete
Db Flowables are observable ,so they keep listening if database changes, so it never completes.
Ideally you should fix userDao so that it completes normally. If that is not possible for some reason, you can time it out and map error to empty, forcing completion like so:
userDao.getInfo(userData.getId())
.timeout(1, TimeUnit.SECOND)
.onErrorResumeNext(Observable.empty())

Categories

Resources