0

My Question: Can you stop a firebase document from being written after a certain time limit? I am attempting to change a user's pin and notify them of success or failure within a time limit. More on my issue below (especially the bold area at the end).

Note: Currently, a user's pin is saved in their local shared preferences. However, there may be a few tablets/phones in the same building and I want them to all have the same pin number. I plan to have the devices check for pin updates once per day. The pin is saved in Shared Preferences for immediate usage, whereas the firestore pin is only checked to update the Shared Preferences pin number.

To change their pin, I now have them enter their current pin, then test this against their app's shared preferences as well as what is saved in the firestore document. Below is all psudocode, but should be close enough to get my point across.

// MAIN ACTIVITY - Global Handler variable
Handler mHandler = new Handler();

...

// Button's OnClick()
if( (pinEntered == sharedPreferencesPin) && (pinEntered == currentFirestorePin)){

    // Connect to firestore and update the new pin
    // OnSuccessListener changes a Singleton boolean value to true or false
    changePin(pinEntered)

    // Show "Please Wait" Loading Class with spinning progress bar widget
    showWaitingScreen();

    // Runnable checks Singleton in 5 seconds to see whether the operation was success or failure
    mHandler(checkSuccessRunnable, 5000);
}

I keep my firebase methods separate, so the next section here is a different class

// Global For PIN UPDATE Class
FirebaseFirestore db = FirebaseFirestore.getInstance();

public void changePin(String pinIn){
    db.collection(pinCollectionPath).document("PIN").set(pinIn)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {

                // Update singleton - Success
                SingletonClass.setPinChangedBoolean = true;
            }
         })
         .addOnFailureListener(new OnFailureListener() {
             @Override
             public void onFailure(@NonNull Exception e) {

                 // Update singleton - Failed
                 SingletonClass.setPinChangedBoolean = false;
             }
         });
}

Back to my Main activity I have my runnable complete. The "checkSuccessRunnable," checks the Singleton boolean value. Depending on the value, I create a success or failure Alert Dialog.

// MAIN ACTIVITY
checkSuccessRunnable= new Runnable() {
    @Override
    public void run() {
            createAlertDialog( SingletonClass.getPinChangedBoolean() );
        }
    }
};

My issue arises when I try to update the firestore pin document while offline. Then, minutes later, successfully writing the document when the Tablet/Phone comes back online. I need to either have a failure or success message within 5 seconds. Obviously, the user wants to know if their pin was changed, or not, immediately.

In testing, I turned my WiFi/Data off and tried to update the Pin document. After the 5 second runnable expired, I get a "Failed to update" message, which is correct. However, after a few minutes, when I turned my phone data back on, the firestore document was immediately updated. The obvious problem is that the user already received a "failure" message after the Runnable completed (5 seconds after clicking the update button).

I originally thought about using a broadcast intent, but the app receives requests to change the current activity, by the facility hardware, every so often. Even if the success message is presented to the user later, they may never see it.

FoxDonut
  • 252
  • 2
  • 14

2 Answers2

1

What you're trying to do has no simple workaround.

When persistence is enabled (default is enabled on Android) writes to documents are initially stored in the persistence layer. The SDK will attempt to synchronize those writes until the write actually fails on the server. Typically this is a result of exceeding some limit or violating a security rule. Network failure are automatically retried silently. The only way to change this behavior is to disable persistence. If you do that, then you will lose all of the documented benefits of the local cache with respect to its offline behavior.

Note that if the app process dies while a write is being synchronized in the background, the synchronization will restart automatically when the app is restarted. However, you will no longer have a callback to use to determine if and when the write succeeded. In that case, you can query for the data you wrote and look at the hasPendingWrites flag on the snapshot metadata to determine if the data is saved.

If you require more control over how all this works, you can always bypass the provided SDK and go straight to the REST API. However, even if you do this, you don't necessarily have a 100% guaranteed result. A network disruption could prevent you from getting the result of a successful write, and the user would never know.

It's up to you which option you choose. Many developers prefer to allow the SDK do its default work because it's simply easier to assume that the write will eventually succeed. But if you don't like that assumption, you'll have to code it yourself.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • 1
    Wow, thank you for all of this. I think you saved me a large portion of reading and I learned a lot from this. I did NOT know about persistence, but as you say, it's better to have enabled as I do much more in the firestore than add a users pin - I record ALL events coming through. Honestly the first 2 paragraphs are gold to me. I don't really want to dig into another API, but it's worth a look. Though this is great info, i'm going to look into running this one as a transaction, as mentioned by @s_o_m_m_y_e_e. Thanks again! – FoxDonut Sep 23 '20 at 03:22
1

There is no direct solution for this, as the Firestore writes are automatically sent to the database as soon as the network is available.

So What Can you do? hmm.

Solutions:

1. CHECK FIRST!: You can first check if the user has an active network connection or not and then make read to Firestore. This would require you to check for active connection (do not rely upon just the system's wifi and internet connection).
I would suggest you look at this for getting more details, of how to perfectly check for an Active Connection: Network connection / Internet access

2. USE TRANSACTIONS: You can use a transaction as it fails if there is no internet connection. So for your use-case, if the user has an active connection, transaction would be executed and if not, the transaction would fail. And most importantly, it will NOT execute automatically.

Happy Coding!

s_o_m_m_y_e_e
  • 406
  • 4
  • 9
  • Thank you for this info on transactions, this is new to me and i'll have to do some digging! As per your network point, I skipped adding that in my post, but I do a test. I basically send a ping to google to ensure I can reach the web, not just wifi or network. I test for MalformedURLException, IOException, and ensure a returned response code = HTTP_OK before calling it good. Not that I need all that, but I make code reusable in case I need it in another project. Thanks again. – FoxDonut Sep 23 '20 at 03:17
  • Using ping to check for network connection is not recommended in any project, as many manufacturers disable pinging. You can refer to the discussion (_for your future network projects_) in the link I gave above in my answer. :-) – s_o_m_m_y_e_e Sep 23 '20 at 03:26