I'm at a loss on this one. I'm unable to delete a node in Firebase either from the web app or through the Firebase console. When the /courses/{courseId}/members/{uid} node is removed, either through set(null) or remove(), the user's information is added immediately.
I do have two cloud functions updating the seats node so we can keep track of space, but neither of those point at the ../{uid} endpoint. I've also gone through my web app code to make sure there were no on_delete events writing to the tree. Any clues as to what's happening?
UPDATE
After going backward in my commit tree, I was able to confirm that it was the below cloud function disallowing deletes from the tree. So, my question now becomes, what in this function causing the behavior? I can't see anything that would re-write data. My database structure is below.
/courses ref
courses: {
someStringId1234: {
members: {
uid12345: {
code: 'someString',
email: 'some#email.com'
},
uid67890: { ... }
},
seats: 10
},
{ ... }
}
This cloud function watches for changes to the uid item. It grabs the parent node (members) and then updates the count in seats. For some reason, this is re-writing the previously deleted key and setting it to 0.
countRegistrations, firebase cloud function
exports.countRegistrations = functions.database.ref('/courses/{courseId}/members/{uid}').onWrite(
(change) => {
// get the `members` node for the item
const collectionRef = change.after.ref.parent;
console.log('collectionRef', collectionRef)
// get the `seats` key for updating
const countRef = collectionRef.parent.child('seats');
console.log('countRef', countRef)
let increment;
// If the ID is there after but not before, remove one seat
if (change.after.exists() && !change.before.exists()) {
increment = -1;
// if the ID is not there after, but there before, add one seat
} else if (!change.after.exists() && change.before.exists()) {
increment = 1;
} else {
// Nothing to change
return null;
}
// Return the promise from countRef.transaction() so the function
// waits for this async event to complete before it exits.
return countRef.transaction((current) => {
console.log('Line 38', current) // debugging, confirms the correct refs are grabbed by the function
return (current || 0) + increment;
}).then(() => {
return
});
});
Just for fun, here's what happens when I try to delete the node directly in the console. The database rules allow writing if you're logged in.
I had the same issue. Turns out that by deleting that node would cause too many firebase functions to execute. Try use the delete command in the CLI, it might give you more information on the error. firebase database:remove <//path to node>
I had same issue, and I just fixed it. The error occurred because of the node data it is using.
Please check where you use the node current, and break or sign out. Then, it is possible to delete the node.
Explanation: If the node is user info and current the user is logged in, it is impossible to delete the node. Continuously, re-created node as long as deleting node.
For this scenario, i am using handler to delay the refresh of my activity with 2-3 seconds, If anyone is getting this error then make sure:-
Do not make the refresh of an activity or display (reloading the database)
Use handler with 2-3 seconds delay, then the logged in user can't auto remake the node.
How i used to delete the node:-
myViewHolder.deleteicon.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String childvalue = list.get(i).getPosition();
Log.d(TAG, "onClick: "+ childvalue);
FirebaseDatabase.getInstance().getReference()
.child("Submission/Rules").child(categorynametext).child(user.getUid()).child(childvalue).removeValue()
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Toast.makeText(context, "You have removed successfully", Toast.LENGTH_LONG).show();
try
{
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
///Here i am refreshing the activity
((Activity)context).finish();
((Activity)context).overridePendingTransition( 0, 0);
((Activity)context).startActivity(((Activity) context).getIntent());
((Activity)context).overridePendingTransition( 0, 0);
}
}, 3000);
}
catch (Exception e){
e.printStackTrace();
}
} else {
//enter msg or enter your code which you want to show in case of value is not remove properly or removed failed.
Toast.makeText(context, "There is an error while removing the rule, Please try again later", Toast.LENGTH_LONG).show();
}
}
});
}
});
UPDATE:
After working more in this, i found i was also getting the same error, because of using addValueEventListener< in this case it is auto adding the data while deleting or adding something in the database and if you use addListenerForSingleValueEvent, then there will not any problem while adding or deleting the data.
Final Conclusion:
addValueEventListener (If you want to refresh the page everytime any add or deletion occurs
addListenerForSingleValueEvent(If you don't want to refresh the page while adding or deleting something)
Related
My app is built with a combination of Provider and Bloc patterns, but moving towards full BLoC. I'm looking for the best way to architect a solution, and basically just where to put this code and whats best practice.
I'm building a booking app, and my user has a field called currentBookingId. When this value is null, the user is left free to use the app completely. However, when the user makes a booking, I'm sending them to a details page.
Using Firebase and streams, where is the best place to listen for changes to this field, and to navigate to the appropriate page? They also need to go straight to this page when opening the app.
At the moment, It's just in my root HomeScreen widget which adds bloat and complexity. This is how I've made it:
#override
void initState() {
super.initState();
final database = Provider.of<DatabaseService>(context, listen: false);
_userStream = database.userStream.listen((user) {
final wasOnTrip = [some logic];
final isOnTrip = [some other logic];
setState(() => _user = user);
if (!wasOnTrip && isOnTrip) {
_navigateToCurrentTrip();
} else if (wasOnTrip && !isOnTrip) {
_navigateHome();
}
});
}
void _navigateToCurrentTrip() {
Navigator.of(context).pushNamedAndRemoveUntil(tripDetailsRoute,
(route) => route.isCurrent && route.settings.name == tripDetailsRoute
? false : true);
}
void _navigateHome() {
Navigator.of(context).popUntil((route) => route.isFirst);
}
I should have dived into the docs a little deeper. Adding a Bloc Listener on both pages works perfectly and is simple to implement.
https://bloclibrary.dev/#/recipesflutternavigation?id=ui-layer-1
I am sending app usage analytics events to Fabric and Firebase. Together with the event, I am also sending another value (an example event type is font_selection and the value I pass is which font the user selects - this is a number that tells me which font was used). I was using Fabric events and I could see which fonts were being used more or less when I selected the font_selection event (I could see numbers for each different font).
Since the Fabric functionality is being moved to Firebase, I started checking the Analytics section in Firebase. Unfortunately I cannot find the above information in Firebase > Analytics > Events. I can see the event, font_selection but when I click on it I do not get the additional information I used to get in Fabric. Is there something I am missing or has this additional information been removed from Firebase?
This is still an issue for me. Here is how I'm sending the event into Firebase:
protected void Report(string id, Severity severity, string message = null, Exception exception = null)
{
try
{
var processedId = id ?? severity.ToString().ToLowerInvariant();
var values = new Dictionary<string, string>();
values.Add("severity", severity.ToString().ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(message))
{
values.Add("message", message);
}
if (exception != null)
{
values.Add("exception", exception.Message);
values.Add("trace", exception.StackTrace);
}
SendEvent(values, processedId);
}
catch
{
// do nothing.
}
}
protected override void SendEvent(Dictionary<string, string> eventData, string id)
{
var firebaseAnalytics = FirebaseAnalytics.GetInstance(Android.App.Application.Context);
var bundle = new Android.OS.Bundle();
foreach(var pair in eventData)
{
bundle.PutString(pair.Key, pair.Value);
}
firebaseAnalytics.LogEvent(id, bundle);
}
During runtime, I call this successfully and I can see these event popping up in Firebase console:
But how do I display the rest of the properties that I have bundled with it? Here is what the console shows me in events:
I feel like I must be using it wrong or something. There is no UI to shows me a simple chronologically sorted table with events as they came in with properties they came with. I frankly don't understand what good is this tool to me.
I've a problem where I send message once and Subscriber is called once but next time it is called twice and so on... Here's my code.
This is message sender
public void OnSuccess(Java.Lang.Object result)
{
UploadTask.TaskSnapshot taskSnapShot = (UploadTask.TaskSnapshot)result;
string downloadURL = taskSnapShot.DownloadUrl.ToString();
string fileName = taskSnapShot.Metadata.Name;
GBPaperReceipt.Model.ImageFile imageFile = new Model.ImageFile
{
FileName = fileName,
FilePath = downloadURL
};
MessagingCenter.Send((App)Xamarin.Forms.Application.Current, MessageStrings.ImageUploadEvent, imageFile);
//save this live storage image url in receipt table
//MessagingCenter.Send<Xamarin.Forms.Application, string>((Xamarin.Forms.Application)Xamarin.Forms.Application.Current, ChatModuleConstant.UploadMediaEvent, downloadURL);
}
This is message receiver
MessagingCenter.Subscribe<App, ImageFile>((App)Application.Current, MessageStrings.ImageUploadEvent,async (a, imageFile) =>
{
_viewModel.Receipt.ImagePath = imageFile.FilePath;
_viewModel.Receipt.ImageName = imageFile.FileName;
try
{
await DependencyService.Get<IReceiptService>().SaveReceipt(_viewModel.Receipt);
}
catch (Exception ex)
{
await DisplayAlert(
"Error!", ex.Message, "OK");
}
DependencyService.Get<ICamera>().DeletePhoto(_viewModel._imageToBeDeletedOnSaveCommand);
Dialogs.HideLoading();
Application.Current.MainPage = new NavigationPage(new DashboardPage());
});
Unsubscription
protected override void OnDisappearing()
{
base.OnDisappearing();
MessagingCenter.Unsubscribe<App, string>((App)Application.Current, MessageStrings.ErrorEvent);
MessagingCenter.Unsubscribe<App, string>((App)Application.Current, MessageStrings.ImageUploadEvent);
}
Especially when using your page inside a navigationpage, your subscription event will be added whenever the page comes into view. If you navigate back and forward a couple of times, your subscription to the messagingcenter will be added several times causing your event to double fire.
The safest way is to subscribe in the page constructor and even in that case it can be necessary to unsubscribefirst and then subscribe.
Your Appearing/Disappearing approach might work as well, however I am not entirely sure if the Appearing/Disappearing methods give you any guarantee to fire.
However, you also might try moving your unsubscriptions in front of base.OnDisappearing(), since you should unsubscribe before calling the base class to do the internal dismantling of your page.
If that doesn't work, subscribe in the constructor.
I noticed that if I execute a query in Firebase and the database server is not reachable, the callback waits just forever (or until the server is reachable again).
Where this behavior is quite natural for the asynchronous approach used, it would nevertheless be useful to have an easy way to specify a timeout so you could inform the user about the status.
Is there such an option and I just missed it - or it really missing?
Or how would you solve this problem?
you can manage yourself a timer controller that after x seconds remove the listener to you firebase reference. It's very simple, just one line of code in android for example.
You can see the code for the web (Detaching Callbacks section):
https://www.firebase.com/docs/web/guide/retrieving-data.html
or for android (Detaching Callbacks section):
https://www.firebase.com/docs/android/guide/retrieving-data.html#section-detaching
same section for IOS ;)
As per today there is no timeout concept on those listeners. One option is to manage the timeout yourself.
This is how I do it when I also want to display a progress dialog while loading the content.
private void showProgressDialog(boolean show, long time) {
try {
if (progressDialog != null) {
if (show) {
progressDialog.setMessage("Cargando...");
progressDialog.show();
new Handler().postDelayed(new Runnable() {
public void run() {
if(progressDialog!=null && progressDialog.isShowing()) {
progressDialog.dismiss();
Toast.makeText(ActPreguntas.this, "Couldn't connect, please try again later.", Toast.LENGTH_LONG).show();
}
}
}, time);
} else {
progressDialog.dismiss();
}
}
}catch(IllegalArgumentException e){
}catch(Exception e){
}
}
So when you make a request to Firebase you call showProgressDialog(true,5000) and after 5 seconds if the dialog stills there is because it could not connect and you then do what you have to as per the timeout.
On the callback of the Firebase listener you do this showProgressDialog(false,0)
Hope it helps.
Here's my solution for the Firebase iOS SDK, this may be helpful for others:
extension DatabaseReference {
func observe(_ eventType: DataEventType, timeout: TimeInterval, with block: #escaping (DataSnapshot?) -> Void) -> UInt {
var handle: UInt!
let timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { (_) in
self.removeObserver(withHandle: handle)
block(nil)
}
handle = observe(eventType) { (snapshot) in
timer.invalidate()
block(snapshot)
}
return handle
}
}
Usage:
database.child("users").observe(.value, timeout: 30) { (snapshot) in
guard let snapshot = snapshot else {
// Timeout!
return
}
// We got data within the timeout, so do something with snapshot.value
}
I would suggest simply using a thread?
Allow yourself to assign your call to Firebase from within a thread instance, then in the rare event that the write to Firebase takes too long you can just cancel the thread?
let thread = NSThread(target:self, selector:#selector(uploadToFirebase), object:nil)
. . .
func uploadToFirebase(data: Dictionary) {
// Do what you need to here. Just an example
db.collection("posts").document("some unique post id").setData([
"name": "John",
"likes": 0
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
}
Then just create a timer that cancels the thread if the timer fires. If not, just cancel the timer.
If you're using the Firebase SDK v6.5.0 and above, you can use FirebaseOptions's setConnectTimeout (https://firebase.google.com/docs/reference/admin/java/reference/com/google/firebase/FirebaseOptions.Builder.html#setConnectTimeout(int)).
Sample:
Integer connectTimeoutinMillis = 6000; //6 seconds
FirebaseOptions firebaseOptions = FirebaseOptions.builder()
.setCredentials(credentials)
.setDatabaseUrl(Application.firebaseSDKDatabaseUrl)
.setConnectTimeout(connectTimeoutinMillis)
.build();
FirebaseApp.initializeApp(firebaseOptions);
Is there a strategy that would work within the current Firebase offering to detect if the server connection is lost and/or regained?
I'm considering some offline contingencies for mobile devices and I would like a reliable means to determine when the Firebase data layer is available.
This is a commonly requested feature, and we just released an API update to let you do this!
var firebaseRef = new Firebase('http://INSTANCE.firebaseio.com');
firebaseRef.child('.info/connected').on('value', function(connectedSnap) {
if (connectedSnap.val() === true) {
/* we're connected! */
} else {
/* we're disconnected! */
}
});
Full docs are available at https://firebase.google.com/docs/database/web/offline-capabilities.
Updated:
For many presence-related features, it is useful for a client to know when it is online or offline. Firebase Realtime Database clients provide a special location at /.info/connected which is updated every time the client's connection state changes. Here is an example:
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
System.out.println("connected");
} else {
System.out.println("not connected");
}
}
#Override
public void onCancelled(DatabaseError error) {
System.err.println("Listener was cancelled");
}
});
I guess this changed in the last couple of months. Currently the instructions are here:
https://firebase.google.com/docs/database/web/offline-capabilities
In summation:
var presenceRef = firebase.database().ref("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().set("I disconnected!");
and:
var connectedRef = firebase.database().ref(".info/connected");
connectedRef.on("value", function(snap) {
if (snap.val() === true) {
alert("connected");
} else {
alert("not connected");
}
});
I'll admit I don't know a lot about how references are set, or what that means (are you making them out of thin air or do you have to have already created them beforehand?) or which one of those would trigger something on the server as opposed to something on the front end, but if the link is still current when you read this, a little more reading might help.
For android you can make user offline by just a single function called onDisconnect()
I did this in one of my chat app where user needs to get offline automatically if network connection lost or user disconnected from Internet
DatabaseReference presenceRef = FirebaseDatabase.getInstance().getReference("USERS/24/online_status");
presenceRef.onDisconnect().setValue(0);
On disconnecting from network Here I am making online_status 0 of user whose Id is 24 in firebase.
getReference("USERS/24/online_status") is the path to the value you need to update on offline/online.
You can read about it in offline capabilities
Note that firebase takes time around 2-10 minutes to execute onDisconnect() function.
firebase for web
firebase.database().ref(".info/connected").on("value",(snap)=> {});
The suggested solution didn't work for me, so I decided to check the connection by writing and reading 'health/check' value. This is the code:
const config = {databaseURL: `https://${projectName.trim()}.firebaseio.com/`};
//if app was already initialised delete it
if (firebase.apps.length) {
await firebase.app().delete();
}
// initialise app
let cloud = firebase.initializeApp(config).database();
// checking connection with the app/database
let connectionRef = cloud.ref('health');
connectionRef.set('check')
.then(() => {
return connectionRef.once("value");
})
.then(async (snap) => {
if (snap.val() === 'check') {
// clear the check input
await connectionRef.remove();
// do smth here becasue it works
}
});