firebase return onSnapshot promise

14,146

Solution 1

A Promise in JavaScript can resolve (or reject) exactly once. A onSnapshot on the other hand can give results multiple times. That's why onSnapshot doesn't return a promise.

In your current code, you're left with a dangling listener to status_database_ref. Since you don't do anything with the data, it is wasteful to keep listening for it.

Instead of using onSnapshot, use get:

onlineUsers(callback){
    this.status_database_ref.where('state','==','online').get((querySnapshot)=>{
        callback(querySnapshot.size)
    }) 
}

Or in your original approach:

onlineUsers(){
    return this.status_database_ref.where('state','==','online').get();
}

Solution 2

I know it's too late but here is my solution using TypeScript & Javascript.

TYPESCRIPT

const _db=firebase.firestore;
const _collectionName="users";

    onDocumentChange = (
    document: string,
    callbackSuccess: (currentData: firebase.firestore.DocumentData, source?: string | 'Local' | 'Server') => void,
    callbackError?: (e: Error) => void,
    callbackCompletion?: () => void
) => {
    this._db.collection(this._collectionName).doc(document).onSnapshot(
        {
            // Listen for document metadata changes
            includeMetadataChanges: true
        },
        (doc) => {
            const source = doc.metadata.hasPendingWrites ? 'Local' : 'Server';
            callbackSuccess(doc.data(), source);
        },
        (error) => callbackError(error),
        () => callbackCompletion()
    );
};

JAVASCRIPT (ES5)

var _this = this;
onDocumentChange = function (document, callbackSuccess, callbackError, callbackCompletion) {
    _this._db.collection(_this._collectionName).doc(document).onSnapshot({
        // Listen for document metadata changes
        includeMetadataChanges: true
    }, function (doc) {
        var source = doc.metadata.hasPendingWrites ? 'Local' : 'Server';
        callbackSuccess(doc.data(), source);
    }, function (error) { return callbackError(error); }, function () { return callbackCompletion(); });
};
Share:
14,146
Manspof
Author by

Manspof

Updated on June 16, 2022

Comments

  • Manspof
    Manspof almost 2 years

    I'm using firebase/firestore and I'm looking a way to return promise of snapshot.

    onlineUsers(){
         // i want to return onSnapshot
        return this.status_database_ref.where('state','==','online').onSnapshot();
    }
    

    in other file I did

      componentDidMount(){
        // this.unsubscribe = this.ref.where('state','==','online').onSnapshot(this.onCollectionUpdate) 
        firebaseService.onlineUsers().then(e=>{
            console.log(e)
        })
    }
    

    I get the errors

    Error: Query.onSnapshot failed: Called with invalid arguments.

    TypeError: _firebaseService2.default.unsubscribe is not a function

    if i do this way

    onlineUsers(){
       return  this.status_database_ref.where('state','==','online').onSnapshot((querySnapshot)=>{
            return querySnapshot
        }) 
    }
    

    I get

    TypeError: _firebaseService2.default.onlineUsers(...).then is not a function
    

    in addition, when I do this way

       this.unsubscribe = firebaseService.onlineUsers().then((querySnapshot)=>{
            console.log(querySnapshot.size)
            this.setState({count:querySnapshot.size})
        })
    

    // other file

     onlineUsers(callback) {
        return this.status_database_ref.where('state', '==', 'online').get()
    }
    

    it not listen to change into firebase, means if I change in firebase it's not update or change the size..

    ---- firestore function --- I tried to make firestore function that trigger each time the UserStatus node updated but this take some seconds and it slow for me.

    module.exports.onUserStatusChanged = functions.database
    .ref('/UserStatus/{uid}').onUpdate((change, context) => {
        // Get the data written to Realtime Database
        const eventStatus = change.after.val();
    
        // Then use other event data to create a reference to the
        // corresponding Firestore document.
        const userStatusFirestoreRef = firestore.doc(`UserStatus/${context.params.uid}`);
    
    
        // It is likely that the Realtime Database change that triggered
        // this event has already been overwritten by a fast change in
        // online / offline status, so we'll re-read the current data
        // and compare the timestamps.
        return change.after.ref.once("value").then((statusSnapshot) => {
            return statusSnapshot.val();
        }).then((status) => {
            console.log(status, eventStatus);
            // If the current timestamp for this data is newer than
            // the data that triggered this event, we exit this function.
            if (status.last_changed > eventStatus.last_changed) return status;
    
            // Otherwise, we convert the last_changed field to a Date
            eventStatus.last_changed = new Date(eventStatus.last_changed);
    
            // ... and write it to Firestore.
            //return userStatusFirestoreRef.set(eventStatus);
            return userStatusFirestoreRef.update(eventStatus);
        });
    });
    

    function to calculate and update count of online users

    module.exports.countOnlineUsers = functions.firestore.document('/UserStatus/{uid}').onWrite((change, context) => {
    
        const userOnlineCounterRef = firestore.doc('Counters/onlineUsersCounter');
    
        const docRef = firestore.collection('UserStatus').where('state', '==', 'online').get().then(e => {
            let count = e.size;
            return userOnlineCounterRef.update({ count })
        })
    })
    
  • Manspof
    Manspof about 6 years
    frank, I edited my post. I did your way and it not listen to change in firestore. if I change manually the state to offline, the size I get, not change and I do see it invoke again the query
  • Frank van Puffelen
    Frank van Puffelen about 6 years
    Neither the code in your question, nor in your answer, listens for changes. Both onSnapshot and get() will immediately return the current documents matching the query. The code I've given in my answer now will show you which users are currently online. It's unclear to me at this point what your code is trying to accomplish (it starting to feel like a XY problem).
  • Manspof
    Manspof about 6 years
    I want to listen to 'status' collection. if user signup to firebase it change his status with uid to' online', when he close app it change to 'offline'. I want to get the current size of online users. if user in app and other user is close app so it should show the real and update count
  • Manspof
    Manspof about 6 years
    hey frank, do you any solution?
  • Frank van Puffelen
    Frank van Puffelen about 6 years
    I honestly still don't understand what the goal is here. Or maybe it's that I can't figure out how to translate it into code. How is what your (or my) query now returns different from what you need?
  • Manspof
    Manspof about 6 years
    your query works good, BUT when when I'm on the app and someone new is changed to 'online' or 'offline' the counter is not changed. the counter not calculate again, it not listen to changes! in addition, I edited my post and did it with firestore function but it take like 3-5 seconds to update it and it slow for me
  • Patrick
    Patrick over 3 years
    I wish I could provide 2 upvotes: one for the correct solution and one for giving the solution in TypeScript!
  • Abhishek Tomar
    Abhishek Tomar almost 2 years
    Thanks! @Patrick - just accept (Click the tick button) the answer if this is helpful for you.