2

I'm new to Cloud Firestore and trying to figure things out. I'm currently trying to create a simple database for Android that contains the ID of a user and the time they installed the app so I can know if they passed their trial period. Everything seems very simple except getting the date and time (which I want to get from the server). People are saying I should use:

FieldValue.serverTimestamp()

But others say that doesn't work for adding a user. Also, this method returns something called "FieldValue", what is that, and can it be used to compare the user's date and time with the current date and time?

String deviceId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
    
FirebaseFirestore db = FirebaseFirestore.getInstance();
// Create a new user with an ID and time
Map<String, Object> user = new HashMap<>();
user.put("userID", deviceId);
user.put("timeStamp", FieldValue.serverTimestamp());

// Add a new document with a generated ID
db.collection("users")
    .add(user)
    .addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
        @Override
        public void onSuccess(DocumentReference documentReference) {
            Log.d("document", "DocumentSnapshot added with ID: " + documentReference.getId());
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.w("document", "Error adding document", e);
            }
        });
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • That code looks fine to me. When you run this code and step through it in a debugger, which line doesn't do what you expect it to do? – Frank van Puffelen May 05 '21 at 14:31
  • @FrankvanPuffelen ok so if I want find a user with a specific userID and get their timeStamp and compare it with the current time how do I do that? –  May 05 '21 at 15:09
  • None of the code you shared does any reading or querying, so I recommend starting with https://firebase.google.com/docs/firestore/query-data/get-data and posting back here if you run into problems with that part of the use-case. – Frank van Puffelen May 05 '21 at 15:13

1 Answers1

0

First of all, using the "deviceId" as a unique identifier for your users in Firestore doesn't seem to me like a good idea. The main reason is that a user can change the device at any point in time, meaning that all the data under "users/$deviceId/" will be lost. So the best approach, in this case, is to authenticate your users with Firebase and use as a unique identifier the UID that comes from the authentication process.

To write a user object to the database, you can simply use the following lines of code:

Map<String, Object> user = new HashMap<>();
user.put("userID", deviceId);
user.put("timeStamp", FieldValue.serverTimestamp());
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
CollectionReference usersRef = rootRef.collection("users");
usersRef.document(uid).set(user).addOnSuccessListener(/* ... */);

Now, to read back the value of the "timeStamp" property, you need to use a "get()" call, as explained in the following lines of code:

usersRef.document(uid).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DocumentSnapshot> task) {
        if (task.isSuccessful()) {
            DocumentSnapshot document = task.getResult();
            if (document.exists()) {
                Date timeStamp = document.getDate("timeStamp");
            } else {
                Log.d(TAG, "No such document");
            }
        } else {
            Log.d(TAG, "get failed with ", task.getException());
        }
    }
});

Having the "timeStamp" object, now you can get the current date and time and compare them as needed in your project.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • Does Firebase authentication require the user to manually login? –  May 05 '21 at 15:36
  • 1
    There are multiple types of authentication. It's true there is an "email and password" authentication but there also providers, like Google, that provide authentication with a single click. Give it a try an tell me if it works. – Alex Mamo May 05 '21 at 16:01
  • Sorry it's taking a long time as I'm setting up Firebase Authentication for the first time. Just one question: does this mean that the user will be authenticated according to their google account? If yes then doesn't that mean the user can get a new trial period for the app by just switching to another google account? –  May 05 '21 at 17:06
  • 1
    If the user switches to another Google account, yes, it will be able to get another trial. But this can be done in the case of any app. If you don't want that to happen, you should consider restricting the trial in a way that doesn't matter if it has a single account or multiple accounts. Besides that, you can also create device management, if you want. That way you can have a single Google account with multiple devices. – Alex Mamo May 05 '21 at 17:11
  • So in setting up Firebase Authentication I'm stuck at "You must pass your server's client ID to the requestIdToken method." I'm not sure where to find this client ID? If I go to the cloud console as instructed it just asks me to create a new project –  May 05 '21 at 17:28
  • 1
    That's a different question that's not really related to the first. Try searching for how to add Firebase Authentication to your project, or ask a whole new question describing your new problem. Firebase Authentication solution was a bonus, but you can use my last code, and it will work perfectly fine. Something like: `usersRef.document(deviceId).get().addOnCompleteListener(/* ... */)`. Does it work in this simpler manner? – Alex Mamo May 05 '21 at 17:36
  • Yeah I think using hardware identifiers like ANDROID_ID is just less of a headache. If the user doesn't like it tough luck for them :) –  May 05 '21 at 17:38
  • Hey just one more question: How do I get the current time from the server directly in the code? Is there perhaps a way to convert `FieldValue.serverTimestamp()` to a Date object? –  May 05 '21 at 19:35
  • also `document.getDate("timeStamp");` in the code above is always returning null. I checked the Firestore console and the timeStamp field does exist –  May 05 '21 at 20:12
  • 1
    No, the `FieldValue` object that is returned by `FieldValue.serverTimestamp()` doesn't contain any time or date values in it, it's **only** a token. So you cannot convert it. When you pass this token as a value for a field when writing a document, Firestore writes a Timestamp value to the field. So you need to read the document in order to get that Timestamp. – Alex Mamo May 06 '21 at 05:28
  • 1
    If you are getting null most likely that field doesn't exist, and this is because you never wrote successfully. Always attach a listener to the set() operation and check to see if something goes wrong. – Alex Mamo May 06 '21 at 05:30
  • I'm not getting a null value anymore. I guess it was just some error on my part. –  May 06 '21 at 09:37
  • I need to get the current time from the server so I can compare it with the user's timeStamp. Is there any way to directly get the current time from the server? –  May 06 '21 at 09:39
  • 1
    There is no current time on the server. You should write the timestamp as you already do, and then compare it the current time and date – Alex Mamo May 06 '21 at 09:58
  • Ok just one more final question: When the app reads the timeStamp from the FireStore database does it always convert it to the user's time zone? –  May 06 '21 at 12:21
  • 1
    For a better understanding please check Doug's [excellent answer](https://stackoverflow.com/questions/55714631/firestore-timestamp-fromdate-not-utc) regarding this topic. – Alex Mamo May 06 '21 at 12:27