We are able to get Firestore to download records to the local cache and can read those records. However, we are unable to update a record and not able to add a new record.
Have even tried some of the same code (https://github.com/firebase/quickstart-android) but don't seem to be able to get the offline write/insert working - only read.
When we call this function offline it gets called OnFailureListner and throws an exception
#Override
public void onRating(Rating rating) {
// In a transaction, add the new rating and update the aggregate totals
addRating(mRestaurantRef, rating)
.addOnSuccessListener(this, new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "Rating added");
// Hide keyboard and scroll to top
hideKeyboard();
mRatingsRecycler.smoothScrollToPosition(0);
}
})
.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "Add rating failed", e);
// Show failure message and hide keyboard
hideKeyboard();
Snackbar.make(findViewById(android.R.id.content), "Failed to add rating",
Snackbar.LENGTH_SHORT).show();
}
});
}
You can't simply write data offline because Firestore can get the data and upload it whenb you have an Internet connection (which requires a task going in the background). Firestore has improved its offline capabilities but you can always choose to create your own service to upload it when the device connects.
Android lets you create Services. From developer.android.com:
A service is a component that runs in the background to perform
long-running operations or to perform work for remote processes. A
service does not provide a user interface. For example, a service
might play music in the background while the user is in a different
application, or it might fetch data over the network without blocking
user interaction with an activity. Another component, such as an
activity, can start the service and let it run or bind to it in order
to interact with it. A service is implemented as a subclass of Service.
So you could create a Service which keeps waiting for an Internet connection and just calls the FirebaseFirestore object to upload all the data you want.
This may help you, this is the official Android information about services: https://developer.android.com/guide/components/services.html
Related
I'm trying to log in in Firebase from these type of services in Android.
I'm able to successfully login anonymously from my main activity this way:
private FirebaseAuth mAuth;
(more code)
mAuth.signInAnonymously().addOnSuccessListener(this, new OnSuccessListener<AuthResult>() {
#Override
public void onSuccess(#NonNull AuthResult authResult) {
(more code to execute when the user has logged)
But if I try to use the same thing from a Service or JobIntentService it's impossible to do, as the first parameter for addOnSuccessListener needs to refer to an activity.
I tried to do a cast in the service to Activity to that "this", but, as expected, it didn't work.
I also tried with:
mAuth.signInAnonymously().addOnCompleteListener(
But the same happens with the first parameter, it needs to be an activity.
Under some circumstances, the mentioned services might run when the main activity is active, but not as a general rule, so I'd need something to allow a service to log in regardless of another circumstance.
Unless this was a restriction for firebase and cannot be done, which I would find pretty strange.
I finally realized a solution:
This code:
while (true){
try {
if (mAuth.getCurrentUser().getUid() == null) {
} else {
break;
}
}
catch (Exception e)
{
}
}
Allows to wait until the user has successfully logged in into firebase.
It can be improved by limiting the number of times the loop executes, but it serves as a general idea.
For the past few days i've been trying to show the online/offline status of a user.. For this i have a register activity where they register and their info gets saved in firebase and if they exit an activity i have overriden its onstop method and made the value to set to offline... but if the user suddenly loses internet connection it still shows online.. i cant change it to offline because internet is needed to make a change in the database and the use doesn't have internet... SO how do i set the database value to offline... i googled quite some stuff about this but didnt find anything... Can anyone please help me out please
My code
#Override
protected void onStart() {
super.onStart();
fetchData();
// mDatabaseReference.child("UserData").child(UID).child("Online").setValue("True");
}
#Override
protected void onStop() {
super.onStop();
fetchData();
// mDatabaseReference.child("UserData").child(UID).child("Online").setValue(false);
}
What you're trying to do is known as a presence system. The Firebase Database has a special API to allow this: onDisconnect(). When you attach a handler to onDisconnect(), the write operation you specify will be executed on the server when that server detects that the client has disconnected.
From the documentation on managing presence:
Here is a simple example of writing data upon disconnection by using the onDisconnect primitive:
DatabaseRef presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().setValue("I disconnected!");
In your case this could be as simple as:
protected void onStart() {
super.onStart();
fetchData();
DatabaseReference onlineRef = mDatabaseReference.child("UserData").child(UID).child("Online");
onlineRef.setValue("True");
onlineRef.onDisconnect().setValue("False");
}
Note that this will work in simple cases, but will start to have problems for example when your connection toggles rapidly. In that case it may take the server longer to detect that the client disappears (since this may depends on the socket timing out) than it takes the client to reconnect, resulting in an invalid False.
To handle these situations better, check out the sample presence system in the documentation, which has more elaborate handling of edge cases.
I'm working with firestore in android. I want to allow my user to save the data in app during the offline mode.(Data insertion during offline is also working fine) But I don't know how I can detect that data is added in offline mode, I need to get document id that is added. In the online mode I can detect the data insertion with the listener as.
Map<String, Object> data = new HashMap<>();
data.put("name", "Tokyo");
data.put("country", "Japan");
db.collection("cities")
.add(data)
.addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
#Override
public void onSuccess(DocumentReference documentReference) {
Log.d(TAG, "DocumentSnapshot written with ID: " + documentReference.getId());
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "Error adding document", e);
}
});
I also need to detect that is added when the app is offline. So how I can? Because these listeners only works when the data is inserted in the server and app get the response from the server.
The addOnSuccessListener only gets called once the data is committed to the server. That's its explicit goal. If you local client also need the data after it's added locally, you'll do that with a regular addSnapshotListener.
From the documentation on events for local changes:
Local writes in your app will invoke snapshot listeners immediately. This is because of an important feature called "latency compensation." When you perform a write, your listeners will be notified with the new data before the data is sent to the backend.
Retrieved documents have a metadata.hasPendingWrites property that indicates whether the document has local changes that haven't been written to the backend yet. You can use this property to determine the source of events received by your snapshot listener.
See the linked documentation for sample code of how to process this.
Update: if you're just trying to get the ID of a new document, you can simply do:
DocumentReference newDoc = db.collection("cities").document();
System.out.println(newDoc.getId());
newDoc.set(data);
See CollectionReference.document().
Offline insert listener.
Based on my code test, it is just fine to remove both the success and snapshot listeners. simply check if a document id is not null and its length is greater than 10. that was enough for me to conclude a successful insert. code :
docRef.collection("collectioname").document("docname").set(aMap);
String id = docRef.collection("collectioname").document("docname").getId();
if( id != null & !id.isEmpty() & id.length() > 10) {
//Action here
}
Thank you Frank for your earlier clue. The add() will always write the document to the local .....in my case the set() method.
I'm new to Firebase and I'm having a lot of problems with the fact that all the tasks are called asynchronously.
For example, I am trying to use fetchProvidersForEmail to know if I should direct the user to sign up or log in. However, by the time the task finishes, it's too late.
I am not sure if it's clear but here is my current code (which works) and below is the method I would want to create. How can I get that done?
public static void printProviders(String email) {
FirebaseAuth auth = FirebaseAuth.getInstance();
auth.fetchProvidersForEmail(email).addOnCompleteListener(new OnCompleteListener<ProviderQueryResult>() {
#Override
public void onComplete(#NonNull Task<ProviderQueryResult> task) {
Log.d(TAG, "We have " + task.getResult().getProviders().size() + " results.");
for (int i = 0; i < task.getResult().getProviders().size(); i++) {
Log.d(TAG, "Provider " + (i+1) + ": " + task.getResult().getProviders().get(i));
}
}
}
);
}
Here is the pseudo-code of the method I would want to create (of course, this doesn't work)...
public static boolean emailIsRegistered(String email) {
FirebaseAuth auth = FirebaseAuth.getInstance();
auth.fetchProvidersForEmail(email).addOnCompleteListener(new OnCompleteListener<ProviderQueryResult>() {
#Override
public void onComplete(#NonNull Task<ProviderQueryResult> task) {
if (task.getResult().getProviders().size() > 0) {
return true;
}
return false;
}
});
}
However, this does not work because the return statement is void for onComplete() and because the task is executed asynchronously...
I am new to this. I tried to search through StackOverflow but couldn't find anything that helped me. Hopefully someone can help.
Thank you!
When you call fetchProvidersForEmail that information is not available in the APK of your app. The Firebase client has to call out to the servers to get this information.
Given the nature of the internet, this means that it will take an undetermined amount of time before the result comes back from those servers.
The client has a few options on what to do in the meantime:
wait until the data is available
continue executing and calling you back when the data is available
Waiting for the data would mean that your code stays simple. But it also means that your app is blocked while the data is being looked up. So: no spinner animation would run, the user can't do anything else (which may be fine for your app, but not for others), etc. This is considered a bad user experience. So bad in fact, that Android will show an Application Not Responding dialog if your app is in this state for 5 seconds.
So instead, the Firebase SDKs choose the other option: they let your code continue, while they're retrieveing the data from the servers. Then when the data is retrieved, they call back into a code block you provided. Most modern web APIs are built this way, so the sooner you come to grips with it, the sooner you can efficiently use those APIs.
The easiest way I found to grasps asynchronous programming is by reframing your problems. Right now you're trying to "first determine if the email is already used, then sign the user up or in".
if (emailIsRegistered(email)) {
signInUser(email);
}
else {
signUpUser(email);
}
This approach leads to a emailIsRegistered method that returns a boolean, something that is impossible with asynchronous methods.
Now let's reframe the problem to "determine if the email is already used. When we know this, sign the user up or in".
This leads to a different piece of code:
public static boolean emailIsRegistered(String email) {
FirebaseAuth auth = FirebaseAuth.getInstance();
auth.fetchProvidersForEmail(email).addOnCompleteListener(new OnCompleteListener<ProviderQueryResult>() {
#Override
public void onComplete(#NonNull Task<ProviderQueryResult> task) {
if (task.getResult().getProviders().size() > 0) {
signUserIn(email);
}
signUserUp(email);
}
});
We've moved the calls to sign the user up or in into the emailIsRegistered method and invoke then when the result becomes available.
Now this of course hard-coded the follow up action into the emailIsRegistered method, which makes it harder to re-use. That's why you quite often see a callback being passed into these functions. A great example of that is the OnCompleteListener that you're already using. Once the Firebase client gets the result from the servers, it calls the onComplete method that you passed in.
Learning to deal with asynchronous calls is both hard and important. I'm not sure if this is my best explanation of the concepts ever. So I'll include some previous explanations (from both me and others):
Setting Singleton property value in Firebase Listener
Firebase Android: How to read from different references sequentially
Is it possible to synchronously load data from Firebase?
Knowing when Firebase has completed API call?
Gathering data from Firebase asynchronously: when is the data-set complete?
What is callback in Android?
So I disconnected my internet connection and tested my app. When I add something to the database, I check if it was successfully added using the default OnSuccessListener. However, even when my application is in Airplane mode or there is no internet connection, OnSuccessListener is getting called, and OnFailureListener isn't.
This is weird, it shouldn't get called.
Code:
database.child("blah").child(key).setValue(objects).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(#NonNull Void T) {
//Do whatever
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
//display error message
}
});
But for some reason, OnSuccessListener is still being called. This is unbelievably irritating. Even when I add an OnCompleteListenerinstead with if(task.isSuccess()) it does the same.
ِAs The Doc Says:
Firebase apps remain responsive even when offline because the Firebase
Realtime Database SDK persists your data to disk. Once connectivity is
reestablished, the client device receives any changes it missed,
synchronizing it with the current server state.
so your data has successfully added to the disk and once you go online it will be synchronized to the cloud.