0

I'm creating a chat app and I want some way to keep track of the messages. I've already read the following threads on the topic:

Firebase firestore collection count

How to get a count of number of documents in a collection with Cloud Firestore

How to keep track of listeners in Firebase on Android?

And I manage to get a count, it works, but the counter starts over every time I close the app?

I have this method to find the number of Docs in a Firestore collection:

 public void numberOfMessagesInConversation() {

    CollectionReference messageRef = db.collection("users")
            .document(userID)
            .collection("conversations")
            .document("conversation0") //Specified conversation
            .collection("messages");

    messageRef.addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

            counter = documentSnapshots.size();
            Log.d(TAG, "counter: " + counter);
        }
    });
}

Then I have another method where I call the method above, add 1 to the counter and return it as a String:

public String createAndReturnMessageSentNumberStringForDocName() {

    numberOfMessagesInConversation();
    counter++;
    String messageNumberForDoc = String.valueOf(counter);
    Log.d(TAG, "createAndReturnMessageSentNumberStringForDocName: " + messageNumberForDoc);

    return messageNumberForDoc;
}

Lastly, I have a method to upload the message to Firestore, and I use the counted number to name the messages there, by creating names for the documents such as "message 1", "message 2", "message 3" etc. based on the counter.

        public void addSentMessageToFirestoreDB(Map<String, Object> messageSent) {

            String docNumber = createAndReturnMessageSentNumberStringForDocName();
            Log.d(TAG, "docNumber: " + docNumber);
    }

When I open the app, and I write the first messages, it correctly ads the messages in chronological order, and it keeps accurately track of the messages. However, when I close the app and re-open it, it starts to count from a lower number? I have defined the counter at the top like so:

int counter;

So I don't reset it to zero during the initiation either.

Here are my log outputs in chronological order:

I'm also confused why the "createAndReturnMessageSentNumberStringForDocName" method seems to be called first in the log? I thought the "numberOfMessagesInConversation" method would run first.

My new code after feedback from Alex Mamo:

public class ChatCloudSentMessageToFirestore {
private static final String TAG = "saveMessageSent";
int messageCounter;

// Initialize Firebase Firestore Database
private FirebaseFirestore db = FirebaseFirestore.getInstance();


// Initialize Firebase Authentification
private FirebaseAuth mAuth = FirebaseAuth.getInstance();
String userID = getCurrentFirebaseUserId();

public String getCurrentFirebaseUserId() {
    FirebaseUser user = mAuth.getCurrentUser();
    String userID = user.getUid();
    return userID;
}

//Specified conversation
CollectionReference messageCollectionRef = db.collection("users")
        .document(userID)
        .collection("conversations")
        .document("conversation0") //Specified conversation
        .collection("messages");


private interface FirestoreCallback {
    void onCallback(int messageCounter);
}

private void readData(FirestoreCallback firestoreCallback) {
    messageCollectionRef.addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

            messageCounter = documentSnapshots.size();
            messageCounter++;

        }
    });
    firestoreCallback.onCallback(messageCounter);
}


public int getMessageCount() {
    readData(new FirestoreCallback() {
        @Override
        public void onCallback(int messageCounter) {

        }
    });
    return messageCounter;
}

public void addSentMessageToFirestoreDB(final Map<String, Object> messageSent) {
    WriteBatch batch = db.batch();

    DocumentReference chrisSentMessageRef = db.collection("users")
            .document("ypiXrobQxuZ0wplN5KO8gJR7Z4w1")
            .collection("conversations")
            .document("conversation0") //Specified conversation
            .collection("messages")
            .document("message" + Integer.toString(getMessageCount()) + " (sent)");


    DocumentReference friendSentMessageRef = db.collection("users")
            .document("LnUDNBVLW3PM7Dd7dbVJgwLzPe03")
            .collection("conversations")
            .document("conversation0")
            .collection("messages")
            .document("message" + Integer.toString(getMessageCount()) + " (sent)");

    batch.set(chrisSentMessageRef, messageSent);

    batch.set(friendSentMessageRef, messageSent);

    batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
                @Override
                public void onComplete(@NonNull Task<Void> task) {
                    ;
                }

            });
}

And I'm calling the addSentMessageToFirestoreDB method from a fragment like so:

//Writes the message to the database only
    sendMessageButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            String textMessage = editTextChatInputBox.getText().toString();

            if (textMessage.length() != 0) {
                FirebaseUser user = mAuth.getCurrentUser();
                String userID = user.getUid();

                Map<String, Object> textMessageHashmap = new HashMap<>();
                textMessageHashmap.put("From user with ID", userID);
                textMessageHashmap.put("Message", textMessage);
                textMessageHashmap.put("Message number in conversation", chatCloudSentMessageToFirestore.getMessageCount()
                        );

                chatCloudSentMessageToFirestore.addSentMessageToFirestoreDB(textMessageHashmap);
            }
            editTextChatInputBox.setText("");

        }
    });
    return rootView;
The Fluffy T Rex
  • 430
  • 7
  • 22

2 Answers2

0

You cannot use something now, that hasn't been loaded yet. With other words, you cannot simply make the the counter global and use it outside the onEvent() method because it will always be null due the asynchronous behaviour of this method. This means that by the time you are trying to use that result outside that method, the data hasn't finished loading yet from the database and that's why is not accessible.

A quick solve for this problem would be to pass the counter variable as an argument ot the createAndReturnMessageSentNumberStringForDocName() method or to use it only inside the onEvent() method.

I thought the "numberOfMessagesInConversation" method would run first.

No it won't. The reson is because the this asynchronous behaviour.

If you want to use it outsise, I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • I implemented with interface and callback method based on your video. The problem still persists. Exactly the same problem – The Fluffy T Rex Aug 23 '18 at 15:45
  • Waiting for the data it shouln't be an issue. Please add the code that you are using. There is something wrong for sure. – Alex Mamo Aug 23 '18 at 15:57
  • The problem in your code is that you are using the following line of code: `firestoreCallback.onCallback(messageCounter);` outside that callback. Move it inside the `onEvent()` method, right? – Alex Mamo Aug 23 '18 at 16:12
  • Still have the same problem :-( – The Fluffy T Rex Aug 24 '18 at 11:00
  • Have you put it right after this line of code `messageCounter++;`? – Alex Mamo Aug 24 '18 at 11:03
  • Yes, it's within the onEvent scope and right after messageCounter++; – The Fluffy T Rex Aug 24 '18 at 11:53
  • Oh, I saw now the problem. Even if you are waiting for the data, you cannot return it in the way do it. To solve this, change your `getMessageCount()` method to `void` and use remove `return messageCounter;` and use `messageCounter` only inside the `onCallback()` method. Does it work now? – Alex Mamo Aug 24 '18 at 11:58
  • So none of these methods can return the counter so I can use it elsewhere? If I put all the logic of storing the message in Firestore within the onCallback() method it creates an infinite loop and writes hundreds of duplicate messages to the database – The Fluffy T Rex Aug 24 '18 at 15:35
  • Not as a result of the method, Firebase is an asynchronous API. So you can take a look at **[this](https://stackoverflow.com/questions/50118345/firestore-merging-two-queries-locally/50132229)** and **[this](https://stackoverflow.com/questions/51892766/android-firestore-convert-array-of-document-references-to-listpojo/51897261)** to see another approach. – Alex Mamo Aug 24 '18 at 15:49
0

I just want to post an update to my own question regarding how I proceeded and solved the original issue.

I asked Firebase directly and they advised me to use Distributed Counters / Shards.

Here's the documentation for Distributed Counters

This worked.

The Fluffy T Rex
  • 430
  • 7
  • 22