When using Firebase, it is recommanded to use the push() command to manage data list. That's really fine, it provides a unique and ordered id for data pushed on the list.
However, when Firebase goes offline (goOffline or NetworkOffline), if the app try to push a data on a list, the completion listener is not triggered, until the app goes back online : so there is no unique id until the line is on again.
1/ Is that the expected/normal behavior ?
2/ I didn"t see in the document (as far I remember), that the push command is working differently (or only in onlinemode) in offline state. Did I miss a line somewhere ?
3/ My use case deal with data that own relationship. It means I want create an object (kind of master) in a list, and then reuse this master object id (provided by the completion listener) to build the relation between the master object and all other relevants objects. How may I deal with this offline state ?
Code Example :
findViewById(R.id.button3).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new Firebase(getString(R.string.firebase_url)).child("stack").push().setValue(counter++, new Firebase.CompletionListener() {
#Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
((TextView) findViewById(R.id.textView3)).setText(String.valueOf(counter) + " - " + firebase.toString());
}
});
}
});
Edit
Here is 4 ways to add data:
Push with Listener
findViewById(R.id.button3).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new Firebase(getString(R.string.firebase_url)).child("stack").push().setValue(counter++, new Firebase.CompletionListener() {
#Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
((TextView) findViewById(R.id.textView3)).setText(String.valueOf(counter) + " - " + firebase.toString());
}
});
}
});
SetValue with Listener
findViewById(R.id.button4).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new Firebase(getString(R.string.firebase_url)).child("stackManual").child(UUID.randomUUID().toString()).setValue(counter++, new Firebase.CompletionListener() {
#Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
((TextView) findViewById(R.id.textView4)).setText(String.valueOf(counter) + " - " + firebase.toString());
}
});
}
});
SetValue without Listener
findViewById(R.id.button5).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Firebase temp = new Firebase(getString(R.string.firebase_url)).child("stackManual").child(UUID.randomUUID().toString());
temp.setValue(counter++);
((TextView) findViewById(R.id.textView5)).setText(String.valueOf(counter) + " - " + temp.getKey().toString());
}
});
Push without Listener
findViewById(R.id.button6).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Firebase temp = new Firebase(getString(R.string.firebase_url)).child("stack").push();
temp.setValue(counter++);
((TextView) findViewById(R.id.textView6)).setText(String.valueOf(counter) + " - " + temp.getKey().toString());
}
});
IT APPEAR THAT THE LISTENERS ARE TRIGGERED ONLY WHEN THE DATA ARE IN THE DATABASE ON THE SERVER, AND NOT ON THE LOCAL DB !
- IS THAT CORRECT ?
- IS THAT DOCUMENTED ?
Because knowing that, a straight forward asynchronous application is more difficult to build now : encapsulation of asynchronous job may not be performed if offline, may it ?
so there is no unique id until the line is on again
This last statement is not true. Firebase push ids are generated client-side and are statistically guaranteed to be unique.
You can easily check this by splitting your operation:
Firebase ref = new Firebase(getString(R.string.firebase_url));
Firebase newStackRef = ref.child("stack").push();
System.out.println("New key/push id: "+newStackRef.key());
newStackRef.setValue(counter++, new Firebase.CompletionListener() {
The logging output will show you the new push id, even when you're not connected to the network.
IT APPEAR THAT THE LISTENERS ARE TRIGGERED ONLY WHEN THE DATA ARE IN THE DATABASE ON THE SERVER, AND NOT ON THE LOCAL DB! IS THAT CORRECT ?
The completion listeners will only trigger once the data has been committed on the server.
From the guide on saving data:
If you'd like to know when your data has been committed, you can add a completion listener. Both setValue() and updateChildren() take an optional completion listener that is called when the write has been committed to the database.
However, regular event listeners (like those created with addValueEventListener()) will be triggered both for local and remote edits. These are the listeners you should use to render your UI, etc.
Following what #Andrew-lee and #Frank-fan-puffelen (thank to both agree on that please), the solution appear to be :
Create a unique id with push() command
Add a listener (Single Value) to a firebase ref of the previous created node in (1.)
Schedule your job inside the listener (here update the UI, but could be another command)
findViewById(R.id.button7).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Firebase temp = new Firebase(getString(R.string.firebase_url)).child("stack").push();
temp.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
((TextView) findViewById(R.id.textView7)).setText(String.valueOf(counter) + " - " + dataSnapshot.getKey().toString());
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
temp.setValue(counter++);
}
});
Related
I want to start a Fragment with information I have to get from the Firebase database when I click the button. For the sake of an example, say I want to open a user profile and I need to load all data from database > users > uid AND I need information from database > messages (e.g. the posts of the user).
Now my approach would be this:
(The following code is a part of the #Override public void onClick(View v) { } method of the button that shall start the Fragment)
dbRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
User user = snapshot.getValue(User.class);
// start the fragment here ...
}
});
But since I need information from database > messages as well, I'm not sure how to do this in a good way. The following would be possible:
(The following code is a part of the #Override public void onClick(View v) { } method of the button that shall start the Fragment)
dbRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
User user = snapshot.getValue(User.class);
// just add another listener on the other reference
dbRef_2.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
MessagesInfo message = snapshot.getValue(MessagesInfo.class);
// start the fragment here ...
}
});
}
});
But this seems not to be the best way to do it since we just got two SingleValueEvent listeners nested. And if we'd need more information, like 10 different database "locations", we'd need to nest 10 of these listeners.
So what is a good way?
Unfortunately, there is no method to fetch multiple locations at once, you could do this with a custom script if you are expecting to re-use it multiple times but it would always result in a loop that fetches each item.
It's also important to note that the order in which the queries were sent is also the order in which the server processes them.
is it possible for android to automatically update the data that is stored in real time database? For example, the data that is gonna be stored in my firebase real time database is going to be constantly changing within seconds because it is connected to a back-end system that triggers its change. Could it be done that every time the data has changed, the textView in my application that shows that data gets updated every time as well ? For now, I have a button that updates the data on click, but is it possible to update it automatically without a button? My code looks like this for now :
a = findViewById(R.id.insidetempView);
b = findViewById(R.id.filterView);
c = findViewById(R.id.humiditytextView);
d = findViewById(R.id.CO2textView);
Button saveButton = findViewById(R.id.numbersButton);
saveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
reff = FirebaseDatabase.getInstance().getReference().child("Numbers");
reff.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
String CO2 = snapshot.child("CO2").getValue().toString();
String filter = snapshot.child("Filter").getValue().toString();
String humidity = snapshot.child("Inside_humidity").getValue().toString();
String temp = snapshot.child("Inside_temperature").getValue().toString();
a.setText(temp);
b.setText(filter);
c.setText(humidity);
d.setText(CO2);
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
});
This blog post should provide you with the solution you need go through it and you will be able to do what you want https://firebase.googleblog.com/2017/12/using-android-architecture-components.html
it has information on using android Architecture Components with Firebase Realtime Database. and when you observe LiveData you can implement all changes instantly
So I am using Firebase Realtime Database and I want to remove a listener as soon as a certain criteria is met. Here is my code:
final DatabaseReference forRequests = FirebaseDatabase.getInstance().getReference(Common.requests + "/" + FirebaseAuth.getInstance().getUid());
listenForRequests = forRequests.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
pickuprequest.riderUID = (String) dataSnapshot.child(Common.riderUID).getValue();
if (pickuprequest.riderUID != null) {
forRequests.removeEventListener(listenForRequests);
showRequestOnMap(forRequests);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
System.out.println("The read failed: " + databaseError.getCode());
}
});
}
listenForRequests is a global variable. Just wondering if this code will work, or if there are any better solutions to this as I feel like it is very hacky.
I can't really tell what exactly your condition is really trying to express, but if you want just a single value a single time from the database (without listening to its changes over time), you can simply use addListenerForSingleValueEvent() to get a single snapshot of a node in the database.
If you're waiting for a value to appear that wasn't previously there, and you want to stop listening at the time it appears, what you're doing is fine. But you might want to listen more closely to the child of interest instead of its parent.
Your code looks pretty idiomatic to me when you want to wait for a specific value.
In fact, the code in my gist on waiting for an initial value is pretty similar:
mListener = ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.exists()) {
System.out.println("The value is now "+snapshot.getValue());
ref.removeEventListener(mListener);
}
}
...
I am a beginner and I am making a simple android game in which a user sign in into the app and play the game. After the game finishes I want to add the current score of the user with previous score and then store this total score in Firebase and then again retrieve the score next time when the user plays game.
I am not getting on how should I save the Total score for every different user who sign in.
this is my firebase json tree
https://drive.google.com/file/d/1T7-x3TP1TaA8_ntwfoRNdb2oMGV_swl6/view?usp=sharing
private void updateScore() {
TotalScore = new Firebase("https://bscitquiz.firebaseio.com/Users/" + username +"/highScore");
TotalScore.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Integer totalscore = dataSnapshot.getValue(Integer.class);
totalscore = totalscore + mScore;
dataSnapshot.getRef().setValue(totalscore);
HighScore.setText("" +totalscore);
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
}
You can save each user's score under its name at the database and just update it each time.
the android java code is written like this -
public static void updateScore(Integer score, String userId) {
DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
ref.child("Users").child(userId).addListenerForSingleValueEvent(new ValueEventListener() {
#Override public void onDataChange(DataSnapshot dataSnapshot) {
Integer previousScore = dataSnapshot.getValue(Integer.class);
if (previousScore != null){
previousScore = previousScore + score;
dataSnapshot.getRef().setValue(previousScore);
}
}
#Override public void onCancelled(DatabaseError databaseError) {
Log.e("frgrgr", "onCancelled", databaseError.toException());
}
});
}
This way you retrieve the old score, update it, and send back to the database.
Now at this way you have only the score value under each user.
You can use the user's name for the child ref of "Users" if its unique or just generate an Id for each user.
Edit after understanding your need for a callback -
callback in android?
For adding a callback there are a few steps,
open a new java class with the following code -
public interface FireBaseCallbacks {
interface GetScoreCallback {
public void onGetScoreComplete(Integer score)
}
}
add the callback parameter to your updateScore method -
private void updateScore(FireBaseCallbacks.GetDevicesCallback callback) {
...
}
call the callback parameter after you get the value -
Integer totalscore = dataSnapshot.getValue(Integer.class);
callback.onGetScoreComplete(totalscore)
call the method somewhere in your activity (at the point that you want to update the score) with this as a parameter to tell the callback where to come back to (regarding the activity) -
updateScore(this)
now for the last step, implement the FireBaseCallbacks.GetScoreCallback to your activity and then you'll be forced to implement its onGetScoreComplete method. implement it and inside it you get the score as a parameter to do what ever you wish :)
Hope this will help, let me know otherwise.
You can have a path for score under User (for Eg: User/userId/score) where you can update the score each time after completing the game. Below is the javascript code.
firebase.database().ref('User').child(userId).child('score').set(currentScore).then(result => {
const user = snap.val()
const userKey = snap.key
}).catch (function(err) {
console.error(err)
})
Here currentScore is the value you want to update.
You can go through this link if you want it in android: https://firebase.google.com/docs/database/android/read-and-write
in my Android app I'm deleting some data from my Firebase Database tree, in a transaction because I want to delete multiple paths from the tree at the same time. The path that is deleted in a transaction is the following:
pseudocode:
begin transaction
for all eventId's in a list delete member with memberid
/contexts/uid/contextId/events/eventId/members/memberId
end for
then delete member from
/contexts/uid/contextId/members/memberId
commit
end transaction
I use the following java code in my android app and it works very well
database.getReference(Global.DEV_PREFIX).runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
for (String eventKey : eventKeySet) {
mutableData.child("/contexts/" + getCurrentUserId() + "/" + contextId + "/events/" + eventKey + "/members/" + member.getId()).setValue(null);
}
mutableData.child("/contexts/" + getCurrentUserId() + "/" + contextId + "/members/" + member.getId()).setValue(null);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
if (databaseError == null) {
callback.onSucess();
} else {
callback.onError(databaseError);
}
}
});
But now I want to have a cloud function that listens on the deletion of the member and deletes some files from the storage.
I did a lot of tests and I can not understand the behaviour. The cloud functions are triggered when deleting the member in a non transactional way. But when deleting in a transaction, the .onDelete event is never triggered in the server (nor the onWrite event). My Cloud function is defined in the following way:
exports.memberDeleteTrigerTest = functions.database.ref('/contexts/{userId}/{contextId}/members/{memberId}/')
.onDelete(event => {
console.log('Hello memberDeleteTrigerTest');
return ;
});
I tried to listen to a inner property of the member without success:
exports.memberDeleteTrigerTest = functions.database.ref('/contexts/{userId}/{contextId}/members/{memberId}/id')
.onDelete(event => {
console.log('Hello memberDeleteTrigerTest');
return ;
});
Maybe the way I delete the data in the transaction in the Android app is not the appropriate. Do you have any suggestions?
Thanks in advance