I realize similar questions have been asked here, but previous solutions do not seem to apply. I'm developing an android app using Firebase auth, and I'm trying to keep track of the user handles that are registered by means of a Firebase database. This is very similar to Tanishq Sharma's answer to firebase duplicate username and custom sign-in.
My problem is that the onDataChange method of the ValueEventListener is never called. As suggested by Frank van Puffelen in Firebase with java (non-android) retrive information, I tried to add some wait time, but this did not solve the problem. After the wait time has passed, the onCancelled method is always called.
The code below shows the function that should return true if a user handle is listed in the Firebase database at node "takenHandles". I know that the connection to the database is working, because I'm able to add a new handle to the database by another part of my code without any issues. Any suggestions on what could be causing this problem?
EDIT It seems I don't have permission to comment on my own question. Thanks Frank, for your quick reply! That indeed explains why writing to the database works, because I do that just after a new user has been authenticated. I should probably consider changing the database rules.
public boolean handleTaken(final String mHandle) {
final boolean[] isTaken = new boolean[]{true};
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference takenHandles = database.getReference("takenHandles");
takenHandles.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.d(TAG, "ValueEventListener: onDataChange");
isTaken[0] = dataSnapshot.hasChild(mHandle);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d(TAG, "ValueEventListener: onCancelled");
Toast.makeText(MyAccountActivity.this, "Connection error, please try again", Toast.LENGTH_SHORT).show();
}
});
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return isTaken[0];
}
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. :]
I've a weird behavior in my app related to the firebase database.
I got some unexpected access denied when trying to perform some stuff at database... That could be bug in my code but is a pretty simple code.
I want to ilustrate the sitation with a real scenario, please read the requirements
1- i've only 1 app accessing the database (android)
2- i've only 1 method in the whole app trying to access the specific node which is causing deny of access
3- in my firebase rules the only rule applied to this node is:
".read":"auth != null",
".write":"auth != null"
4- i DO explicity check FirebaseAuth.getInstance().getCurrentUser() != null right before calling the method
this is a pseudo snippet of how my code is (i wont pust the real code simple because is too long)
public void onResume() {
if(FirebaseAuth.getInstance().getCurrentUser() == null)
doLogin();
else
checkFirebaseStuff();
}
As google doesn't provide any info about why the access was rejected the only possible explanation i found is:
the user was authenticated but long time before, so when it checked on if was still valid, but short time later when the function really ran its token (or some other firebase auth check) was no longer valid, so it caused the access denied
this error doesn't happen a lot, i have 5k daily users and it happens around 20 or 50 times a day, but still shouldn't happen even once
does it make sense? can anyone help me with any aditional info?
Hmm, I think you can fix this by checking if the user is disconnected from the firebase database, but I'm not really sure if that will affect the Auth too, you can give it a try
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
Log.d(TAG, "connected");
//Here you can update your mAuth state
} else {
Log.d(TAG, "not connected");
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
Log.w(TAG, "Listener was cancelled");
}
});
My app is using Firebase and there are almost 200 users live at the a given time. Most of the users are complaining that the data doesn't load. I was using ChildEventListener for obtaining the data which keep the connection alive and reflects live changes. There is a limit of 100 connections in the free plan. I guess that is the reason my data is not loading at times. After reading the doc I found another way to read data using ValueEventListener. Below is the code I'm currently using
public void getImages() {
Query imagesQuery = FirebaseDatabase.getInstance().getReference().child("englishDps").child(mChildName).orderByKey().limitToLast(21);
ChildEventListener childEventListener = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Image image = dataSnapshot.getValue(Image.class);
image.setNodeKey(dataSnapshot.getKey());
mTempImages.add(image);
if (mTempImages.size() == 21) {
mLastKey = mTempImages.get(0).getNodeKey();
Collections.reverse(mTempImages);
mTempImages.remove(mTempImages.size() - 1);
mImages.addAll(mTempImages);
setAdapter();
}
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
if (isAdded()) {
Toast.makeText(getActivity(), "Problem loading more images...", Toast.LENGTH_LONG).show();
}
}
};
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot imageSnapshot : dataSnapshot.getChildren())
{
Image image = imageSnapshot.getValue(Image.class);
image.setNodeKey(imageSnapshot.getKey());
mTempImages.add(image);
if (mTempImages.size() == 21) {
mLastKey = mTempImages.get(0).getNodeKey();
Collections.reverse(mTempImages);
mTempImages.remove(mTempImages.size() - 1);
mImages.addAll(mTempImages);
setAdapter();
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
// imagesQuery.addChildEventListener(childEventListener);
// imagesQuery.addValueEventListener(valueEventListener);
imagesQuery.addListenerForSingleValueEvent(valueEventListener);
}
According to the docs
"While using a ChildEventListener is the recommended way to read lists
of data, there are situations where attaching a ValueEventListener to
a list reference is useful.
Attaching a ValueEventListener to a list of data will return the
entire list of data as a single DataSnapshot, which you can then loop
over to access individual children.
Even when there is only a single match for the query, the snapshot is
still a list; it just contains a single item. To access the item, you
need to loop over the result:.
This pattern can be useful when you want to fetch all children of a list in a single operation, rather than listening for additional
onChildAdded events."
I was thinking this will solve the data loading problem but my previous version of the app will still keep using live connection and I'm still seeing random success and failures for data load call in new version of the app with more than 150+ users live right now on old version of the app. What will happen if the old version of the app opens more than 100 connection and the new version of the app tries to load data ? i.e. if 100 connections in the free plan are used will a query with
imagesQuery.addListenerForSingleValueEvent(valueEventListener);
succeed or fail ?
When an Android app first uses the Firebase Database SDK, it makes a connection to the Firebase servers. If there are at that moment already as many connection as are allowed to your database, the new connection will be rejected. The type of listener has no influence on this.
For a lot of discussions covering this already, see this list. Some good ones:
Limitation of free plan in firebase
How the Connection is calculated in Firebase
When are new connections allowed after limit of 100 concurrent connection is reached in firebase?
How exactly are concurrent users determined for a Firebase app?
How to limit concurrent connections on Firebase Android
Having looked at your code. I recommend inserting a closing connection once the read of images from json are completed. In the free package there is a limit of connections so once they read the images, they're technically still connected.
Looking at your Datasnapshot, they don't do anything but still querying the Firebase. I also recommend look into indexing too.
https://firebase.google.com/docs/database/rest/save-data
https://firebase.google.com/docs/database/security/indexing-data
Following situation:
User not registered, I save data while offline (Firebase setOffline())
Cannot read the local data (populate listview etc) - the ValueEventListener and ChildEventListener dont fire
I set setOnline() on Firebase instance
Data is synced with web and displayed (listeners fire)
I set setOffline() again.
I save local data and read local data, works (listeners fire)
Question:
How to read local data stored BEFORE going online?
Scenario is: User uses the android app offline and decides later to register
Scenario 1:
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
FirebaseDatabase.getInstance().goOffline(); // <--------NOTE THIS
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
mDatabase.child("users").child(App.get().getUid()).child("items").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// THIS IS NOT FIRING
}
#Override
public void onCancelled(DatabaseError databaseError) {
...
}
});
After Scenario 1 I change code to this and run:
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
FirebaseDatabase.getInstance().goOnline(); // <--------NOTE THIS
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
mDatabase.child("users").child(App.get().getUid()).child("items").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// THIS IS FIRING. ALL GOOD
}
#Override
public void onCancelled(DatabaseError databaseError) {
...
}
});
After this I change code to following and it works
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
FirebaseDatabase.getInstance().goOffline(); // <--------NOTE THIS
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
mDatabase.child("users").child(App.get().getUid()).child("items").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// THIS IS FIRING. ALL GOOD
}
#Override
public void onCancelled(DatabaseError databaseError) {
...
}
});
I added 3 segments (code blocks).
I execute first block - does not work
Second block - works
Third block - works
Here is a gist with the code.
Problem is that first block does not work before being online with setOnline()
Since you force the client to go offline in scenario 1 before it has a chance to synchronize any data, I indeed expect it to not fire onDataChange() in that scenario. In the 3rd fragment it will fire, because it has had a chance to synchronize data to the local cache.
But why are you explicitly trying to manage online/offline state? By doing this you're digging a hole that you may find it hard to get out of.
If you want to avoid having the user sign-in, you can start off with Anonymous Authentication and then upgrade that to a email/password or social account later.
Just keep in mind that starting offline and only enabling synchronizing later is not an ideal way of working with the Firebase Database, which is primarily an online database that continues working offline.
So I went through the documentation about how to retrieve datain Firebase: https://firebase.google.com/docs/database/android/retrieve-data
In my firebase app I am retrieving the values using the addListenerForSingleValueEvent.
Those values retrieved(here userIdKey) in the onDataChange method are used to make updates to some other places in the database.
What I have observed is that sometimes the updates are made everywhere except
at 123 (refer the code below).
DatabaseReference channelFollowersRef = mDatabase.child("followers").child(mKey);
channelFollowersRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot childSnapshot:dataSnapshot.getChildren()){
String userIdKey=childSnapshot.getKey();
/*123*/ delChannelMap.put("/user-subscriptions/" + userIdKey + "//" + mChannelKey,null);
}
delChannelMap.put("/channels/" + mChannelKey, null);
delChannelMap.put("/user-channels/" + getUid() + "/" + mChannelKey, null);
delChannelMap.put("/channels-notices/" + mChannelKey, null);
delChannelMap.put("/channels-subChannels/" + mChannelKey, null);
delChannelMap.put("/channels-subChannels-comments/" + mChannelKey, null);
delChannelMap.put("/channel-followers/" + mChannelKey, null);
mDatabase.updateChildren(delChannelMap, new DatabaseReference.CompletionListener() {
#Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
Toast.makeText(ChannelDetailActivity.this, "Channel is now deleted.", Toast.LENGTH_SHORT).show();
}
});
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
How to solve this issue.
Also can someone elabrate on how onDataChange works , does it get called when all the values at the node are retrieved or 'some' of the data is retrieved ?
Should one use AsyncTask in such a scenario when retrieving data.
onDataChange gives you all the underlying child nodes at a single time whenever there is a change in the child nodes.
Firebase when retrieve data it uses a different thread to perform its task so no need of using AsyncTask.
the problem in your code maybe due to this String userIdKey=childSnapshot.getKey();...where the childSnapshot.getKey(); is not firing the data u want.. You should check in LOGCAT if data is retrieved correctly.
If it is not clear yet please add Json Data of your database so that it could be easier to understand what is happening.
Your code seems to delete fanned out data from many locations.
When you perform a multi-location update() such as this, the security rules for all nodes are checked before any of the changes are made. So (unless there is a bug in the way Firebase enforces these specific security rules), the disconnect can't be caused by security rules.
If your other nodes are updated, but /user-subscriptions/" + userIdKey + "//" + mChannelKey is not deleted, then you are likely not getting any children in dataSnapshot (and thus not passing them in delChannelMap). You'll want to run through the code in a debugger to verify that.
Whenever troubleshooting issues such as this, be sure to handle onCancelled(). It is the easiest way to get an indication of why an operation failed. Although I don't think it will execute here (after all, the write operation seems to succeed), I recommend you attach a completion callback to setValue:
ref.setValue("My new value", new DatabaseReference.CompletionListener() {
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
throw databaseError.toException();
}
});
Throwing an exception like this ensures that it will be very difficult to overlook such an error next time.