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
}
});
Related
I'm running my integration-tests in flutter and dart using the firestore emulator. First I start the firestore emulator with some data like so: firebase emulators:start --import=./dir.
Then I start an android emulator and start the app I want to test on the android emulator. The app is configured to use the firestore emulator. Then I run a series of tests, which all write to the firestore emulator.
But on the beginning of each test, I want the data to be reset to the state, when I first started the emulator. So e.g. if the tests are executed in this order:
Test A
Test B
Test C
I don't want to have the data, Test A created to be present in the database, when Tests B and C are executed. I could terminate the firestore emulator and start it again at the beginning of each test. But this would make my tests a lot slower.
Do you know of a way to reset the data, which is present in the firebase emulator?
I am assuming you're referring to firestore when you say you want to 'reset the data'.
Per the documentation at https://firebase.google.com/docs/emulator-suite/install_and_configure#use_the_emulator_hub_rest_api
import fetch from 'node-fetch';
import firebaseConfig from '../../../firebase.json';
const hubEmulatorPort = firebaseConfig.emulators.hub.port;
const firestoreEmulatorPort = firebaseConfig.emulators.firestore.port;
async function clearDb() {
const response = await fetch(
`http://localhost:${firestoreEmulatorPort}/emulator/v1/projects/${process.env.PROJECT_ID}/databases/(default)/documents`,
{
method: 'DELETE',
}
);
if (response.status !== 200) {
throw new Error('Trouble clearing Emulator: ' + (await response.text()));
}
}
async function populateDb(data) {
// Logic for adding in any data you want in the db
// before each test run
}
async function enableBackgroundTriggers() {
const response = await fetch(`http://localhost:${hubEmulatorPort}/functions/enableBackgroundTriggers`, {
method: 'PUT',
});
if (response.status !== 200) {
throw new Error('Trouble enabling database triggers in emulator: ' + (await response.text()));
}
}
async function disableBackgroundTriggers() {
const response = await fetch(`http://localhost:${hubEmulatorPort}/functions/disableBackgroundTriggers`, {
method: 'PUT',
});
if (response.status !== 200) {
throw new Error('Trouble disabling database triggers in emulator: ' + (await response.text()));
}
}
async function resetDb(data) {
await disableBackgroundTriggers();
await clearDb();
await populateDb(data);
await enableBackgroundTriggers();
}
export { resetDb };
I can't find a source for the clearing of the db, but the RESTful call in clearDb does what you want.
It's important to disable the triggers before clearing or populating the database, in case you have firestore triggers that modify data in ways your tests don't expect. I write tests by passing full DB state to the populateDb method, then reenable triggers before running tests so I can test said triggers. If you aren't running any firestore triggers, the clearDb call alone should be enough for your purposes.
My tests all have calls to resetDb() in my beforeEach hook in jest to ensure clean runs of each test. I recommend adding this to whatever 'beforeEach'-like hook your favorite testing API exposes.
If your tests do things like create users in Firebase Authentication you'll have to find another way to clear them between test runs.
If anyone can find documentation on how to clear other emulators in the Firebase Emulator Suite, please drop it in the comments. I am currently trying to find a way to clear Authentication emulators, which is actually how I found this question.
Best of luck!
If you want to clear out all the collections programatically, like in a setUp() or tearDown() there's a reference for that here: Delete data from Cloud Firestore - Delete Collections
Note that it's not recommended for all implementations, but there are examples in Java, Python, Node.js, go, PHP, C#, and Ruby.
Here's an example of how to iterate through all your collections and delete them all in Java, using the deleteCollection() method from that link.
public static void main(String[] args) throws IOException {
final int BATCH_SIZE = 5;
Firestore firestore = initializeCloudFirestore();
for (CollectionReference listCollection : firestore.listCollections()) {
deleteCollection(listCollection, BATCH_SIZE);
}
}
/**
* One way of initializing Firestore,
* see other options at https://firebase.google.com/docs/firestore/quickstart#initialize
*/
private static Firestore initializeCloudFirestore() throws IOException {
// Use the application default credentials
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(credentials)
.setProjectId("projectId")
.build();
FirebaseApp.initializeApp(options);
Firestore firestore = FirestoreClient.getFirestore();
return firestore;
}
/**
* Delete a collection in batches to avoid out-of-memory errors. Batch size may be tuned based on
* document size (atmost 1MB) and application requirements.
* See https://firebase.google.com/docs/firestore/manage-data/delete-data#java_5
*/
static void deleteCollection(CollectionReference collection, int batchSize) {
try {
// retrieve a small batch of documents to avoid out-of-memory errors
ApiFuture<QuerySnapshot> future = collection.limit(batchSize).get();
int deleted = 0;
// future.get() blocks on document retrieval
List<QueryDocumentSnapshot> documents = future.get().getDocuments();
for (QueryDocumentSnapshot document : documents) {
document.getReference().delete();
++deleted;
}
if (deleted >= batchSize) {
// retrieve and delete another batch
deleteCollection(collection, batchSize);
}
} catch (Exception e) {
System.err.println("Error deleting collection : " + e.getMessage());
}
}
For the entire file, including imports, see this Github Gist.
I'm developing a web app and I use Firebase Authentication for the authentication service.
The project seems to store the authentication, since if I refresh the page, or close the browser, the user is still logged in.
However I noticed that if I don't access the app for a long time (more than 1 hour, after the night for example), the authentication gets lost.
I don't know how to debug this and how to solve this.
Following some snippets of code to better understand my implementation:
This is the function I have in my startup view to redirect the user to the right page based on auth status.
bool isUserLoggedIn() {
var user = _firebaseAuth.currentUser;
return user != null;
}
void handleStartupBasedOnAuthStatus() {
Future.delayed(const Duration(milliseconds: 1000), () async {
bool loggedInShared =
await sharedPreferences.getBoolSharedPreferences("loggedIn");
if (isUserLoggedIn() || loggedInShared) {
String ruoloValue =
await sharedPreferences.getSharedPreferences('ruolo');
(ruoloValue == Ruolo.ADMIN)
? navigationService.replaceWith(Routes.admin)
: navigationService.replaceWith(Routes.messages);
} else {
navigationService.replaceWith(Routes.login);
}
});
}
In the following function I call the onAuthStateChange to set sharedpreferences accordingly. I have the check on the timestamp because I noticed that it is triggered more time once the page is refreshed.
void listenToAuthChangesSharedPref() {
FirebaseAuth.instance.authStateChanges().listen((firebaseUser) async {
var datetimeNow = (DateTime.now().millisecondsSinceEpoch);
String oldDatetimeString =
await sharedPreferences.getSharedPreferences('previous_timestamp');
if (oldDatetimeString != null) {
var oldDatetime = (new DateTime.fromMillisecondsSinceEpoch(
int.parse(oldDatetimeString)))
.millisecondsSinceEpoch;
if (datetimeNow - oldDatetime > 1000) {
if (firebaseUser == null) {
await sharedPreferences.setBoolSharedPreferences('loggedIn', false);
} else {
await sharedPreferences.setBoolSharedPreferences('loggedIn', true);
}
await sharedPreferences.setSharedPreferences(
'previous_timestamp', datetimeNow.toString());
}
} else {
if (firebaseUser == null) {
await sharedPreferences.setBoolSharedPreferences('loggedIn', false);
} else {
await sharedPreferences.setBoolSharedPreferences('loggedIn', true);
}
await sharedPreferences.setSharedPreferences(
'previous_timestamp', datetimeNow.toString());
}
});
}
My question is: is possible that after long time currentUser and also the onAuthStateChanges gets called and the user is not logged in?
Persisting authentication state#
The Firebase SDKs for all platforms provide out of the box support for ensuring that your user's authentication state is persisted across app restarts or page reloads.
On native platforms such as Android & iOS, this behaviour is not configurable and the user's authentication state will be persisted on-device between app restarts. The user can clear the apps cached data via the device settings which will wipe any existing state being stored.
On web platforms, the user's authentication state is stored in local storage. If required, you can change this default behaviour to only persist authentication state for the current session, or not at all. To configure these settings, call the setPersistence() method (note; on native platforms an UnimplementedError will be thrown):
// Disable persistence on web platforms
await FirebaseAuth.instance.setPersistence(Persistence.NONE);
for more info:
for more info:
I am using Firestore in flutter application. Each time user launch the application it retrieves some data from Firestore Cloud.
QuerySnapshot dataSnapshot = await Firestore.instance
.collection('/data')
.getDocuments();
When user opens the application on first time, it required from him to connect online, to get the data, and as Firebase documents say
For Android and iOS, offline persistence is enabled by default. To disable persistence, set the PersistenceEnabled option to false.
So, it should save the data that application have been read before to retrieve it while the device is offline; so user can access application at anytime with the same data that have been read.
The problem is: it takes too long time to retrieve the data while the device is offline, with the same codes and nothing changed!.
I tried to configure how much time it takes? On offline, it takes about 8 minutes and 40 seconds. But while on online, it takes just 10 seconds, maybe less.
So how can I solve this problem?
============
UPDATE
I manged to get more logs about this problem, which after take a lot of time, and will start application with the offline saved data, it prints this log
This typically indicates that your device does not have a healthy Internet connection at the moment. The client will operate in offline mode until it is able to successfully connect to the backend.
And then take 3 second for example (not much time) and continue with the next works.
I did open a new issue in GitHub too.
Is there a way to limit the time it takes?
And finally, with the help of diegoveloper comment in GitHub issue, I have reached the solution.
This comment
await Firestore.instance
.collection("Collection")
.getDocuments(source: source)
was a good solution if I decided to check source each time and then use it or I can use it in starting of a new Flutter project, but now I already have a lot of codes that need a better solution. So I decided to fork the cloud_firestore package and edit it.
You can find it here: https://github.com/ShadyBoshra2012/flutterfire/tree/master/packages/cloud_firestore
What I have edited:
firestore.dart
// The source of which the data will come from.
static Source _source = Source.serverAndCache;
static Source get source => _source;
Future<void> settings(
{bool persistenceEnabled,
String host,
bool sslEnabled,
bool timestampsInSnapshotsEnabled,
int cacheSizeBytes,
Source source}) async {
await channel.invokeMethod<void>('Firestore#settings', <String, dynamic>{
'app': app.name,
'persistenceEnabled': persistenceEnabled,
'host': host,
'sslEnabled': sslEnabled,
'timestampsInSnapshotsEnabled': timestampsInSnapshotsEnabled,
'cacheSizeBytes': cacheSizeBytes,
});
if (source != null) _source = source;
}
query.dart
source = Firestore.source; Line 92
document_reference.dart
source = Firestore.source; Line 83
How you can use it?
So you can use my forked repository in this way with using connectivity package from Google : https://pub.dev/packages/connectivity .
Add my forked repository in pubspec.yaml file
cloud_firestore:
git:
url: https://github.com/ShadyBoshra2012/flutterfire.git
path: packages/cloud_firestore
Then in your first screen or main
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.none) {
await Firestore.instance.settings(source: Source.cache);
} else {
await Firestore.instance.settings(source: Source.serverAndCache);
}
and if you want to refresh the source when change the connection state:
StreamSubscription subscription;
void initState() {
super.initState();
// Check the internet connection after each change
// of the connection.
subscription = Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) async {
// Check the internet connection and then choose the appropriate
// source for it.
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.none) {
await Firestore.instance.settings(source: Source.cache);
} else {
await Firestore.instance.settings(source: Source.serverAndCache);
}
});
}
#override
void dispose() {
super.dispose();
subscription.cancel();
}
So I hope it works with everyone see it, and waiting for Flutter Team to code a better and better solution. Thanks for everyone has participated.
In addition to Shady Boshra's answer you can use FirebaseFirestore.instance.disableNetwork() functionality so your code will look like this:
StreamSubscription subscription;
void initState() {
super.initState();
// Check the internet connection after each change
// of the connection.
subscription = Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) async {
// Check the internet connection and then choose the appropriate
// source for it.
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.none) {
await FirebaseFirestore.instance.disableNetwork();
} else {
await FirebaseFirestore.instance.enableNetwork();
}
});
}
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)
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);