I am using the new Firebase console. I am trying to save an object to the database but its not getting saved. Following is the code.
DatabaseReference mFirebaseRef = FirebaseDatabase.getInstance().getReference();
mFirebaseRef.push().setValue(object, new DatabaseReference.CompletionListener() {
#Override
public void onComplete(DatabaseError databaseError, DatabaseReference reference) {
if (databaseError != null) {
Log.e(TAG, "Failed to write message", databaseError.toException());
}
}
});
the 'object' is created properly. There is no error I am getting and I have checked in debugging that the onComplete method is not getting triggered.
Security Rule is also true for write.
Note that DatabaseReference.CompletionListener fires "when an operation has been acknowledged by the Database servers". Is it possible you did not have a network connection at the time you ran your test? I copied your code and ran it successfully on a phone with object defined like this:
Object object = new String("Test");
I then enabled airplane mode, re-ran the code and observed the behavior you describe. The completion listener did not fire until I disabled airplane mode and a network connection was established.
Example code for checking the status of your network connection is provided in this documentation.
use the below code:
mFirebaseRef.push().setValue(object).addOnCompleteListener(new OnCompleteListener<Void>()
{
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful()){
// write code for successfull operation
}
}
});
}
.push will generate a random key on which your data will stored if you do not use .push it will overwrite on previous stored data.
Related
I have this code to read data and write the value into an object
if(SpecialFunction.isNetworkAvailable()) {
databaseContent.loadAccountFromDatabase(account -> {
this.account = account;
binding.textView3.setText(account.toString());
});
} else {
startActivity(new Intent(this, InternetTroubleActivity.class));
}
Where databaseContent is a class which contains Firebase logic which I need.
In loadAccountFromDatabase I have next code which works with one problem.
public void loadAccountFromDatabase(FirebaseCallbackAccount accountFirebaseCallback) {
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if (snapshot.exists()) {
account = snapshot.getValue(Account.class);
} else {
account = new Account();
setDefaultAccount();
}
accountFirebaseCallback.onCallback(account);
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
Log.e("loadAccountFromDatabase", "Error: " + error.toString());
}
};
database.addValueEventListener(valueEventListener);
}
When I try to read data after internet reconnection or when I turn on internet after onStart() and run loadAccountFromDatabase I get null value of my snapshot.
I have a method (isNetworkAvailable()) which works well and when I don't have internet connection it returns false.
The next part of code doesn't work properly. The snapshot.exists returns true and snapshot.getValue returns null although the value is other.
if (snapshot.exists()) {
account = snapshot.getValue(Account.class);
}
If I run an application with internet connection everything works well while I don't turn off internet and try to read again.
I also tried to use database.get().addOnCompleteListener(onCompleteListener)... but I got the same result. Everything works well while I don't try to read data after internet reconnection.
database.keepSynced(true) also doesn't help.
UPDATE##
Database init:
private final String USER_KEY = "Account", PURCHASES = "purchases";
public void init() {
mAuth = FirebaseAuth.getInstance();
firebaseDatabase = FirebaseDatabase.getInstance();
database = firebaseDatabase.getReference(String.format("%s/%s", USER_KEY, mAuth.getUid()));
}
Structure:
console
UPDATE 2##
To help you understand the problem I add screenshot of log. GREEN box: I run my app with internet connection. The data was read and wrote correct. RED box: I closed my app, destroyed activity, turned off internet. Then I run my app, but method isNetworkAvailable didn't let me run MainActivity. Then I turned on internet and clicked on button. The data was being tried to read but I got object with null variables althrough they are not null. If you think that something is wrong with isNetworkAvailable I tell you that this problem was before I have added it. I was hoping that it would help me to read correct but it didn't help.
logcat
UPDATE 3##
JSON:
{
"Account" : {
"wBOZsnGGywYIpap3cLZodPOWcpt2" : {
"budget" : 100,
"budgetLastMonth" : 0,
"budgetLeft" : 100,
"currencyType" : "USD",
"email" : "bibishka117#gmail.com",
"id" : "wBOZsnGGywYIpap3cLZodPOWcpt2",
"personName" : "новый пользователь"
}
}
}
I found a problem. 4 hours of reading my code and stackoverflow. I don't know how, but in onPause () I had code that writes null object to a database.
So when I tested and turned on / off the Internet, I run it. So I wrote a null object that was not instantly updated on the console, and when I turned on the Internet, the object was updated to zero in the database and then read in my app. :]
When a new user registers in my app using firebase-authentication custom sign in using email and password, I need to update that data into my firestore.
But Firebase only has FirebaseAuth.createUserWithEmailAndPassword(email, password) to create a new account and hence I cannot update my user's username at the same time.
To update the E-Mail in Firestore, I use Firebase cloud functions. Here's the code:
export const onNewUserJoined = functions.auth.user().onCreate((user) => {
//const newUserDisplayName = user.displayName //CAN'T USE THIS. REASON is BELOW
const newUserUID = user.uid
const newUserEmail = user.email
const timeCreated = Date.now()
console.log(`${newUserUID} has joined.`)
return admin.firestore().collection('Agent').doc(`${newUserUID}`).set({"E-Mail": newUserEmail, "Time": timeCreated})
})
OK, great now I have updated the E-Mail and time created in Firestore successfully.
But next challenge is I need to update the user's username in the same Firestore document. I do it instantly after the createUserWithEmailAndPassword() like this:
DocumentReference dDocRef = FirebaseFirestore.getInstance().document(documentPath);
Map<String, Object> updateUsernameAndPhone = new HashMap<>();
updateUsernameAndPhone.put("username", username);
updateUsernameAndPhone.put("phoneData", phoneModel);
dDocRef.update(updateUsernameAndPhone).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Toast.makeText(getApplicationContext(), "Data successfully stored in Firestore", Toast.LENGTH_SHORT).show();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_SHORT).show();
}
});
Now, it depends on who acts first, the cloud function or the user's device.
If the cloud functions act first, then there is no issue. The username and phoneModel both get updated into the document successfully. No issues.
But incase, the phone acts first then I get the following error:
As this error has occurred, username isn't in the document and only email and timeCreated are in the document updated by the cloud function which got late to create document so that user's device can update the username with ease.
I CAN'T use .SET instead of .update() in my app because if I use .set() and the cloud functions create the email and timeCreated fields first. Then the device will DELETE them and put username and phoneModel.
So how can I do this?
I can forcefully delay updating the username by putting it in the next activity so that cloud functions get enough time to do their job, but my signUpActivity asks for username along with email and password edit texts. I don't want to create a separate activity for that.
I used to use .update() when my data was stored in realtime database and it used to create the child even if the path didn't exist. But it looks firestore won't update if the field doesn't exist.
Any solution for this?
I tried as per #DougStevenson said and here's my code:
final String newUserUID = Objects.requireNonNull(signUpAuth.getCurrentUser()).getUid();
final String documentPath = "Agent/" + newUserUID;
FirebaseFirestore fFirestoreRef = FirebaseFirestore.getInstance();
final DocumentReference dDocRef = fFirestoreRef.document(documentPath);
fFirestoreRef.runTransaction(new Transaction.Function<Void>() {
#Nullable
#Override
public Void apply(#NonNull Transaction transaction) throws
FirebaseFirestoreException {
DocumentSnapshot documentSnapshot = transaction.get(dDocRef);
transaction.update(dDocRef, "username", username);
transaction.update(dDocRef, "phoneData", phoneModel);
return null;
}
}).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Toast.makeText(getApplicationContext(), "Data updated in Firestore . . .", Toast.LENGTH_SHORT).show();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_SHORT).show();
}
});
But no luck. It still gives me error: Cannot update a document which does not exist.
Use a transaction to resolve conflicts from multiple clients that are all trying to modify the same document. The transaction handler will be retried on the client if it detects the document was modified before the change could take place.
I have implemented the Firebase Real-Time Database presence system as shown in the official Firebase documentation. I would like to make the database secure so that logged-in users can only write to their own presence entries in the DB. So, on login, the user writes to the reference path /auth/{authId}/connections and at the same time sets up the onDisconnect to remove the value.
Here is the code from the Android app that is setting presence in rtdb:
getFirebaseDatabase().goOnline();
DatabaseReference.goOnline();
// Since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
final FirebaseDatabase database = getFirebaseDatabase();
final DatabaseReference myConnectionsRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/connections");
// Stores the timestamp of my last disconnect (the last time I was seen online)
final DatabaseReference lastOnlineRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/lastOnline");
connectedRef = database.getReference(".info/connected");
presenceChangeListener = connectedRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
DatabaseReference con = myConnectionsRef.push();
// When this device disconnects, remove it
con.onDisconnect().removeValue()
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
// Add this device to my connections list
// this value could contain info about the device or a timestamp too
con.setValue("ANDROID");
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "### Failed to set onDisconnect ###");
e.printStackTrace();
}
});
// When I disconnect, update the last time I was seen online
lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);
}
}
#Override
public void onCancelled(DatabaseError error) {
Log.w(TAG, "Listener was cancelled at .info/connected");
}
});
The problem that I am having is that if the user logs out, the onDisconnect doesn't execute unless I first manually disconnect from rtdb. I'm assuming that the code running on the Real-Time DB gets a permission denied since the auth is no longer valid.
//If I don't go offline first the record in rtdb will not be removed.
DatabaseReference.goOffline();
AuthUI.getInstance().signOut(this)
.addOnCompleteListener(new OnCompleteListener<Void>() {
public void onComplete(#NonNull Task<Void> task) {
// user is now signed out
Log.d(TAG, "Logged out");
application.clearData();
DatabaseReference.goOffline(); //This doesn't cause a presence update here
finish();
}
});
Above is the work-around I'm using, first telling the database to goOffline then to logout. If the user ever gets logged out by another means (the web app is seeing if multiple tabs are using the app and one logs out) the user will be left with a connection not removed.
If I don't call the goOffline() prior to logout, the connection in rtdb will not be removed, even if I force close the application.
I have also verified that I can get everything working fine if I change my rtdb rules to be ".write": "true" <-which is no good. This tells me that there is a permission denied with the onDisconnect running when a user logs out of the auth.
I would like my real-time rules to be something like this.
{
"rules": {
"auth": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
}
}
}
I would have hoped that the onDisconnect would still be able to execute with the auth of the user when the onDisconnect was setup.
When you attach a onDisconnect() handler, you're registering a delayed write on the Firebase servers. Whether that write is allowed is checked both when you attach the handler, and when the handler is triggered. And since your user is signed out when the write is triggered, it get rejected by your rules. There is no configuration option to change this behavior, so you'll have to come up with a different approach.
So, because 1.) the onDisconnect() execution is evaluated against the rules of the RTDB, 2.) the user who setup the onDisconnect() may lose authentication, and 3.) I would like to make the presence system secure for my auth'ed users... I came up with the following solution:
First, write the presence entries to the RTDB under a path that contains both the user's authId and a UUID to make the location "unguessable".
"/presence/" + {auth-uid} + "/connections/" + {UUID}
and setup a .onDisconnect() to remove this value stored at the unguessable location.
Then, setup the RTDB rules to do the following:
do not allow any reading of the presence data
allow users to add/modify data only under their auth directory
allow any user to delete records (they would need to know the unguessable path)
"presence": {
".read": "false",
".write": "false",
"$auth_id": {
"connections": {
"$uuid": {
".write": "(newData.exists() && $auth_id === auth.uid) || !newData.exists()"
}
}
}
}
Finally, setup a trigger function on the RTDB to read the .ref('/presence/{authid}') location and push the user's presence to another user accessible location (I'm pushing it to my Firestore DB). Also, if the user is changing from "online" to "offline" update a lastOnline timestamp to the current time.
This seems like the best solution given my requirements of having reliable and secure presence system. I hope this helps others.
It is an old question but it made me think about a possible solution and came with the following...
use onDisconnect.setValue/removeValue as long as you have control/awareness over the application
use onDisconnect.cancel and delete to data before logging out
I took #FrankvanPuffelen code and modified it but didn't tested it so....
//getFirebaseDatabase().goOnline();
//DatabaseReference.goOnline();
// Since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
final FirebaseDatabase database = getFirebaseDatabase();
final DatabaseReference myConnectionsRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/connections");
// Stores the timestamp of my last disconnect (the last time I was seen online)
final DatabaseReference lastOnlineRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/lastOnline");
connectedRef = database.getReference(".info/connected");
presenceChangeListener = connectedRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
// simple solution to reuse the old unique key-name otherwise current solution is like performing new registration of a new client over and over on the same client. we should use the old unique key-name until logout is performed
String keyName = SharedPrefUtil.INSTANCE.getFirebaseConnectionKeyName(context);
DatabaseReference con;
if (TextUtils.isEmpty(keyName)) {
con = myConnectionsRef.push();
SharedPrefUtil.INSTANCE.setFirebaseConnectionKeyName(context.getApplicationContext(), con.getKey());
}else{
con = myConnectionsRef.child(keyName);
}
// When this device disconnects, remove it
con.onDisconnect().removeValue()
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
// Add this device to my connections list
// this value could contain info about the device or a timestamp too
con.setValue("ANDROID");
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "### Failed to set onDisconnect ###");
e.printStackTrace();
}
});
// When I disconnect, update the last time I was seen online
lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);
}
}
#Override
public void onCancelled(DatabaseError error) {
Log.w(TAG, "Listener was cancelled at .info/connected");
}
});
in the logout method we need to cancel the disconnect
String keyName = SharedPrefUtil.INSTANCE.getFirebaseConnectionKeyName(context);
if (!TextUtils.isEmpty(keyName)) {
final FirebaseDatabase database = getFirebaseDatabase();
final DatabaseReference myConnectionsRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/connections/" + keyName);
// Stores the timestamp of my last disconnect (the last time I was seen online)
final DatabaseReference lastOnlineRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/lastOnline");
// This client/user doesn't need the disconnect functionality
myConnectionsRef.onDisconnect().cancel();
// now we are on our own so we need to remove the key-name from the rmdb
myConnectionsRef.setValue(null);
// remove the key-name from the preferences so we will create a new one in the next login session
SharedPrefUtil.INSTANCE.removeFirebaseConnectionKeyName(context);
// we will not forget to disconnect last time updates
lastOnlineRef.onDisconnect().cancel()
}
AuthUI.getInstance().signOut(this)
I didn't tested it and it will not run as it is missing the SharedPrefUtil implementation
Its a very weird issue with Firebase. I am calling setValue() on a Firebase node and then expecting OnComplete callback function to be called, which is never being called on a particular internet connection. Other firebase queries are also not working on that particular WiFi,
individualUserNode.setValue(user, new DatabaseReference.CompletionListener() {
#Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
if (databaseError == null) {
EventBus.getDefault().post(new MyEvents.UserSaved(true));
} else {
EventBus.getDefault().post(new MyEvents.UserSaved(false));
}
}
});
That's expected. The listener only fires once the server acknowledges the request:
This interface is used as a method of being notified when an operation
has been acknowledged by the Database servers and can be considered
complete
Is there any way to know data which is not uploaded on firebase and is persisting on disk? I have enabled offline capability by using-
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
By doing this my all data remains cached. I want to know which data is uploaded and which isnt.
There is no method in the API that returns an indication of which write operations have not yet been committed to the Firebase server. If you need that status, you can manage it yourself. The setValue() and updateChildren() methods provide two options for getting a callback when the value is committed to the Firebase server. One option is to provide a CompletionListener parameter:
FirebaseDatabase.getInstance().getReference("test")
.setValue("SomeValue", new DatabaseReference.CompletionListener() {
#Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
if (databaseError == null) {
// the data was successfully committed to the server
} else {
// the operation failed, for example permission failure
}
}
});
The other option is to add a listener to the returned Task:
FirebaseDatabase.getInstance().getReference("test")
.setValue("SomeValue").addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
// the data was successfully committed to the server
} else {
// the operation failed, for example permission failure
}
}
});
For both cases, the update is in the local cache immediately after the call to setValue() or updateChildren(), but is not in the server database until the callback fires.