5

I wanted to count app visits whenever any user open the app. I have done following code which is working successfully only when INTERNET IS ON

I have used FirebaseDatabase.getInstance().setPersistenceEnabled(true); for storing data offline and also used myRef.keepSynced(true); to keep syncing.

Here is the code:

    // COUNTER PART
    FirebaseDatabase database = FirebaseDatabase.getInstance();
    DatabaseReference myRef = database.getReference("counter");
    myRef.keepSynced(true);
    myRef.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
            Log.i("FB", "Snapshot: " + dataSnapshot);
            long val = (long) dataSnapshot.getValue() + 1;
            myRef.setValue(val);
            mainBinding.contentLayout.textViewCounterVisits.setVisibility(View.VISIBLE);
            mainBinding.contentLayout.textViewCounterVisits.setText(getString(R.string.total_visits, val));
        }

        @Override
        public void onCancelled(@NonNull DatabaseError error) {
            Log.e("FB", "Error: " + error);
        }
    });

PROBLEM:

When I open the app offline for multiple times its counting offline. When I turned on the Internet, It's just syncing with updated data but I wanted to update count by adding new counts.

For example:

  • Count is 105

  • App 1 - Opening for 10 times (offline)

  • App 2 - Opening for 15 times (online)

  • Now count is 120

  • App 1 goes online and opening app and count is updating 121.

I think it should be 131 if we count all.

Pratik Butani
  • 60,504
  • 58
  • 273
  • 437

2 Answers2

4

I think it should be 131 if we count all.

If the mechanism is genuine as you describd, no, it shouldn't be not even 121, it should be 116 and that's the normal behaviour, since you are using the same counter for both apps. Here is the mechanism:

  • Count is 105
  • App 1 - Opening for 10 times (offline) -> Count is still 105
  • App 2 - Opening for 15 times (online) -> Count is 120 (105 + 15)

While App 1 is offline, it only has knowledge of the initial count of 105 and of those 10 increase operations that were queued. By the time the App 1 comes online, it takes 115 and it overrides the existing 120 value that is in the database at that time. So now, the counter in the database has the value of 115. When App 1 is opened again, the counter will be increased to 116.

This "strage" behaviour takes place because App 1 while offline, doesn't know what App 2 is doing while online and vice versa. Generally speaking, when it comes to incrementing counters in a multi user environment, that's not the way you should go ahead with. In such cases, to have consistent data, you should use transactions, as explained in my answer from the following post:

But be aware that Firebase transactions are not supported for offline use. The transactions can't be cached or saved for later use. This is because a transaction absolutely requires round trip communications with server in order to ensure that the code inside the transaction completes successfully.

So in my opinion, you should increment that count field only when the user is online and using a transaction operation.

P.S. If you consider at some point in time to try using Cloud Firestore, there you can find a very useful increment operation method named FieldValue.increment().

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • 1
    I loved this lines `But be aware that Firebase transactions are not supported for offline use. The transactions can't be cached or saved for later use. This is because a transaction absolutely requires round trip communications with server in order to ensure that the code inside the transaction completes successfully. So in my opinion, you should increment that count field only when the user is online and using a transaction operation.` – Pratik Butani Aug 23 '19 at 05:40
  • I had written my answer. Could you please check? Is it okay or I can do it more efficiently or accurately. – Pratik Butani Mar 12 '20 at 06:46
  • @PratikButani Yes it's good, similar to one of my [answers](https://stackoverflow.com/questions/48307610/how-to-save-users-score-in-firebase-and-retrieve-it-in-real-time-in-android-stud). – Alex Mamo Mar 12 '20 at 08:51
0

This is what I have done finally. Thank you AlexMamo for your descriptive answer.

This code will count visits by the user when the user will open the app.

    // COUNTER PART
    FirebaseDatabase database = AVO.getFirebaseDatabase();
    DatabaseReference myRef = database.getReference("counterNew");
    myRef.keepSynced(true);
    myRef.runTransaction(new Transaction.Handler() {
        @NonNull
        @Override
        public Transaction.Result doTransaction(@NonNull MutableData mutableData) {
            Long score = mutableData.getValue(Long.class);
            if (score == null) {
                return Transaction.abort();
            }
            database.getReference("counter").setValue(score + 1);
            mutableData.setValue(score + 1);
            return Transaction.success(mutableData);
        }

        @Override
        public void onComplete(@Nullable DatabaseError databaseError, boolean b, @Nullable DataSnapshot dataSnapshot) {

        }
    });
    myRef.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
            Long val = (Long) dataSnapshot.getValue();
            mainBinding.contentLayout.textViewCounterVisits.setVisibility(View.VISIBLE);
            mainBinding.contentLayout.textViewCounterVisits.setText(getString(R.string.total_visits, val));
        }

        @Override
        public void onCancelled(@NonNull DatabaseError error) {
            Log.e("FB", "Error: " + error);
        }
    });

I hope it will help someone.

Pratik Butani
  • 60,504
  • 58
  • 273
  • 437