Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cloud_firestore]: missing increment updates when unstable internet connection #12952

Open
1 task done
eli1stark opened this issue Jun 12, 2024 · 4 comments
Open
1 task done
Assignees
Labels
platform: android Issues / PRs which are specifically for Android. platform: ios Issues / PRs which are specifically for iOS. plugin: cloud_firestore type: bug Something isn't working

Comments

@eli1stark
Copy link

eli1stark commented Jun 12, 2024

Is there an existing issue for this?

  • I have searched the existing issues.

Which plugins are affected?

Firestore

Which platforms are affected?

Android, iOS

Description

The issue can be reproduced both on iOS and Android (simulator and physical device). I had to dig deep to be able to narrow it down to a simple function. The gist of the issue is that when the network is disabled and then enabled later, FieldValue.increment() may never reach Cloud Firestore, or maybe it reaches it, but there is some conflict there. When your balance is 0 and you call increment(-10) and then increment(10), you can end up with a balance of -10, which is wrong. You should end up with a balance of 0 always.

This issue can be reproduced with a real network on a real device in real-life conditions, but it's hard and very cumbersome. This is why in my reproduction case, I used firestore.disableNetwork() and firestore.enableNetwork().

Also note that when you call the simulate function once, sometimes it can work as expected. I would say it causes the bug about 40-50% of the time. This is why I created the fastSimulate function that increases the chance of missing events. When we call simulate or fastSimulate, the expectation is that the value will always be 0 after all operations are synced with Firestore because we subtract and add the same amount.

Future<void> simulate() async {
  increment(-1);
  await firestore.disableNetwork();
  increment(1);
  await firestore.enableNetwork();
}

Reproducing the issue

  1. Install firebase_core: 2.31.0
  2. Install cloud_firestore: 4.17.3
  3. Configure Firebase for the Flutter project and have FirebaseOptions.currentPlatform ready.
  4. Import FirebaseOptions to the main.dart file.
  5. Replace colId, docId, and valueId with your values.
  6. Create a field in your document, make it an integer, and assign the value of 0.
  7. Launch the app.
  8. Try to press simulate a couple of times with a delay of 2-3 seconds.
  9. If regular simulate doesn't work, try to press fastSimulate.
  10. After you have completed steps 8 and 9, you should have a value of -1 or lower.
import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
// todo: add your path
import 'your-location-of-firebase-options';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: FirebaseOptionsDevelopment.currentPlatform,
  );
  runApp(const App());
}

// todo: update values
const colId = 'your-collection-name';
const docId = 'your-document-id';
const valueId = 'your-field-name';
final firestore = FirebaseFirestore.instance;

class App extends StatefulWidget {
  const App({super.key});

  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  int id = 0;
  int value = 0;
  StreamSubscription<dynamic>? sub;

  @override
  void initState() {
    super.initState();
    subscribe();
  }

  @override
  void dispose() {
    unawaited(sub?.cancel());
    super.dispose();
  }

  void subscribe() {
    sub = firestore.collection(colId).doc(docId).snapshots().listen(
      (data) {
        setState(() {
          value = data[valueId] as int? ?? 0;
        });
      },
    );
  }

  Future<void> simulate() async {
    increment(-1);
    await firestore.disableNetwork();
    increment(1);
    await firestore.enableNetwork();
  }

  void increment(int value) {
    unawaited(asyncIncrement(value, id++));
  }

  Future<void> asyncIncrement(int value, int myId) async {
    print('[START] $myId - increment($value) ${DateTime.now()}');
    await firestore.collection(colId).doc(docId).update({
      valueId: FieldValue.increment(value),
    });
    print('[END] $myId - increment($value) ${DateTime.now()}');
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.white,
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Row(),
            Text(
              value.toString(),
              style: const TextStyle(
                fontSize: 50,
              ),
            ),
            TextButton(
              onPressed: simulate,
              child: const Text('Simulate (50% reproduction guarantee)'),
            ),
            TextButton(
              onPressed: () async {
                final simulations = <Future<void>>[];
                for (var i = 0; i < 50; i++) {
                  simulations.add(simulate());
                }
                await Future.wait(simulations);
              },
              child: const Text('Simulate (95% reproduction guarantee)'),
            ),
          ],
        ),
      ),
    );
  }
}

Firebase Core version

2.31.0

Flutter Version

3.22.2

Relevant Log Output

No response

Flutter dependencies

Expand Flutter dependencies snippet
Replace this line with the contents of your `flutter pub deps -- --style=compact`.

Additional context and comments

Reproducible on the latest versions as of Jun 14th 2024.

firebase_core: 3.1.0
cloud_firestore: 5.0.1

Screen.Recording.2024-06-12.at.3.37.56.PM.mov
@eli1stark eli1stark added Needs Attention This issue needs maintainer attention. type: bug Something isn't working labels Jun 12, 2024
@TarekkMA
Copy link
Contributor

Hello @eli1stark. Thank you for reporting this bug, Can you please try again with the latest firebase versions?

@TarekkMA TarekkMA added blocked: customer-response Waiting for customer response, e.g. more information was requested. plugin: cloud_firestore platform: android Issues / PRs which are specifically for Android. platform: ios Issues / PRs which are specifically for iOS. and removed Needs Attention This issue needs maintainer attention. labels Jun 14, 2024
@TarekkMA TarekkMA self-assigned this Jun 14, 2024
@eli1stark
Copy link
Author

@TarekkMA Yes, it's still reproducible on the latest versions, forgot to mention that.

firebase_core: 3.1.0
cloud_firestore: 5.0.1

@google-oss-bot google-oss-bot added Needs Attention This issue needs maintainer attention. and removed blocked: customer-response Waiting for customer response, e.g. more information was requested. labels Jun 14, 2024
@TarekkMA
Copy link
Contributor

I've been able to reproduce this issue on the issue/12952 branch. I will inform the team, and we will look into fixing this.

@TarekkMA TarekkMA removed their assignment Jun 24, 2024
@TarekkMA TarekkMA removed the Needs Attention This issue needs maintainer attention. label Jun 24, 2024
@TarekkMA
Copy link
Contributor

Native Android Reproduction: Firebase Android Reproduction.

@Lyokone Lyokone self-assigned this Jun 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
platform: android Issues / PRs which are specifically for Android. platform: ios Issues / PRs which are specifically for iOS. plugin: cloud_firestore type: bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants