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]: setState Causes snapshot.data.docChanges to Reinitialize Without Actual Firestore Changes. #12901

Closed
1 task done
abineshPalanisamy opened this issue Jun 5, 2024 · 6 comments
Assignees
Labels
platform: all Issues / PRs which are for all platforms. plugin: cloud_firestore resolution: solution-provided A solution has been provided in the issue.

Comments

@abineshPalanisamy
Copy link

Is there an existing issue for this?

  • I have searched the existing issues.

Which plugins are affected?

Other

Which platforms are affected?

Android, iOS, macOS, Web, Windows

Description

When the application rebuilds using setState, the StreamBuilder reinitializes, causing snapshot.data.docChanges to act as if it is the initial loading. This can result in the provides duplicate values in the list based on snapshot.data.docChanges without any actual changes in Firestore.

  • Sample to reproduce :
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: defaultFirebaseOptions);
  runApp(MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const ListViewDemo()));
}

// Add your FirebaseOptions details
const defaultFirebaseOptions = FirebaseOptions(
    apiKey: " ",
    authDomain: " ",
    projectId: "",
    storageBucket: " ",
    messagingSenderId: " ",
    appId: " ",
    measurementId: " ");

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

  @override
  ListViewDemoState createState() => ListViewDemoState();
}

class ListViewDemoState extends State<ListViewDemo> {
  List<Employee> employeeData = [];
  final getDataFromFireStore =
      FirebaseFirestore.instance.collection('Add your collection').snapshots();

  Widget _buildListView(AsyncSnapshot<QuerySnapshot> snapshot) {
    for (var change in snapshot.data!.docChanges) {
      if (change.type == DocumentChangeType.added) {
        employeeData.add(Employee.fromSnapshot(change.doc));
      } else if (change.type == DocumentChangeType.modified) {
        int index = employeeData.indexWhere((e) => e.id == change.doc['id']);
        if (index >= 0) {
          employeeData[index] = Employee.fromSnapshot(change.doc);
        }
      } else if (change.type == DocumentChangeType.removed) {
        employeeData.removeWhere((e) => e.id == change.doc['id']);
      }
    }

    return ListView.builder(
      itemCount: employeeData.length,
      itemBuilder: (context, index) {
        final employee = employeeData[index];
        return ListTile(
          title: Text(employee.name),
          subtitle: Text('${employee.designation}, Salary: ${employee.salary}'),
          leading: Text(employee.id.toString()),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Firebase Firestore ListView Demo'),
      ),
      body: Column(
        children: [
          Center(
            child: SizedBox(
              width: 200,
              height: 50,
              child: TextButton(
                onPressed: () {
                  setState(() {});
                },
                child: const Text('Button'),
              ),
            ),
          ),
          const Padding(padding: EdgeInsets.all(5)),
          Expanded(
            child: StreamBuilder(
              stream: getDataFromFireStore,
              builder: (BuildContext context,
                  AsyncSnapshot<QuerySnapshot> snapshot) {
                if (snapshot.hasData) {
                  return _buildListView(snapshot);
                } else {
                  return const Center(
                    child: CircularProgressIndicator(),
                  );
                }
              },
            ),
          ),
        ],
      ),
    );
  }
}

class Employee {
  final int id;
  final String name;
  final String designation;
  final int salary;

  Employee({
    required this.id,
    required this.name,
    required this.designation,
    required this.salary,
  });

  factory Employee.fromSnapshot(DocumentSnapshot snapshot) {
    return Employee(
      id: snapshot['id'],
      name: snapshot['name'],
      designation: snapshot['designation'],
      salary: snapshot['salary'],
    );
  }
}
  • Video output:
flutter_application_1.2024-06-04.17-39-45.mp4

Reproducing the issue

  1. Run the attached sample .
  2. Press Text Button in the Top.
  3. Application get rebuilds.
  4. StreamBuilder gets reinitialized and provides duplicate values in the list based on snapshot.data.docChanges without any actual changes in Firestore..

Firebase Core version

3.0.0

Flutter Version

3.22.1

Relevant Log Output

No response

Flutter dependencies

Expand Flutter dependencies snippet
dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.6
  cloud_firestore: ^5.0.0
  firebase_core: ^3.0.0

Additional context and comments

No response

@abineshPalanisamy abineshPalanisamy added Needs Attention This issue needs maintainer attention. type: bug Something isn't working labels Jun 5, 2024
@TarekkMA
Copy link
Contributor

TarekkMA commented Jun 6, 2024

Hello @abineshPalanisamy Thank you for sharing an issue. I see that _buildListView is being called in build. and it has some logic to add to the list employeeData so it can result in duplicate data.

Can you refactor this and test atain?

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

Hi @TarekkMA

When setState is called, snapshot.data.docChanges gets reinitialized and behaves as if it is the initial loading by providing all the documents in the Firestore collection with the type DocumentChangeType.added. This can cause duplicate values to be built. On initial loading of the application, snapshot.data.docChanges correctly provides documents with DocumentChangeType.added, which is expected and fine. However, upon calling setState, it again provides all documents in the Firestore collection as snapshot.data.docChanges with DocumentChangeType.added, even if there have been no changes or modifications in Firestore.

We performed logic in the _buildListView method based on snapshot.data.docChanges. Therefore, I concluded that snapshot.data.docChanges behaves improperly when setState is called in the application. This improper functioning leads to duplicate values.

Regards,
Abinesh P

@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 7, 2024
@TarekkMA
Copy link
Contributor

Calling add in build method will lead to duplicate values, Can you construct the list employeeData only once and try again?

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

Hey @abineshPalanisamy. We need more information to resolve this issue but there hasn't been an update in 7 weekdays. I'm marking the issue as stale and if there are no new updates in the next 7 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

@google-oss-bot google-oss-bot added the Stale Issue with no recent activity label Jun 19, 2024
@abineshPalanisamy
Copy link
Author

Hi @TarekkMA ,

I believe that the add method is not the cause of the duplicate values. Based on snapshot.data.docChanges, we have added the data from Firestore to the ListView.Builder. Therefore, an improper function of snapshot.data.docChanges leads to the duplicate values.

According to the previous response, when setState is called, snapshot.data.docChanges gets reinitialized and behaves as if it is the initial loading by providing all the documents in the Firestore collection with the type DocumentChangeType.added. This is the root cause of the issue. My concern is why snapshot.data.docChanges gets reinitialized and behaves as if it is the initial loading when setState is called in the application. snapshot.data.docChanges should maintain its state even when we rebuild the app by calling setState.

Regards,
Abinesh P

@google-oss-bot google-oss-bot added Needs Attention This issue needs maintainer attention. and removed Stale Issue with no recent activity blocked: customer-response Waiting for customer response, e.g. more information was requested. labels Jun 19, 2024
@russellwheatley
Copy link
Member

Hey @abineshPalanisamy - The reason this is occurring is because setState refreshes the stream listener and the last event is published. The last event contains all the docChanges i.e. the initial load.

To test, you can add a document to the collection in the console and you will see the docChanges contains only 1 change now, each time you press the button, you will now see 1 change.

I advise you not to setState where you're listening to the stream otherwise it will continue to refresh listening to the stream and the docChanges will always contain the changes from the last time the collection was updated.

@russellwheatley russellwheatley added resolution: solution-provided A solution has been provided in the issue. and removed Needs Attention This issue needs maintainer attention. type: bug Something isn't working labels Jun 25, 2024
@firebase firebase locked and limited conversation to collaborators Jul 26, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
platform: all Issues / PRs which are for all platforms. plugin: cloud_firestore resolution: solution-provided A solution has been provided in the issue.
Projects
None yet
Development

No branches or pull requests

4 participants