Cubelated
Cubelated

Reputation: 67

Flutter StreamBuilder stuck on state waiting after re-navigating back to the page

I am new to flutter and I have a problem with StreamBuilder. When starting, snapshots from firestore are returned but when I navigate to another page, then return back to the page, the connection state of the snapshot is stuck in waiting. When hot reloading or refreshing the page, the data is shown and works fine. The problem only happens when re-navigating back to the page. I think that since it does not add new data or edit the data, the streambuilder will not be updated and won't rebuild.

Initial streambuilder

After re-navigating back

I also used DataTable to show my data in table form, the following code might be a little long.

return StreamBuilder<QuerySnapshot>(
    stream: providerDBsnaps,
    builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
      switch (snapshot.connectionState) {
        case ConnectionState.none:
          return const Text('Something went wrong');
        case ConnectionState.waiting:
          return const LinearProgressIndicator();
        case ConnectionState.active:
          if (snapshot.hasData) {
            final List<DocumentSnapshot> documents = snapshot.data!.docs;

            providers = documents
                .map((e) =>
                    ProviderList.fromMap(e.data() as Map<String, dynamic>))
                .toList();

            List<DataRow> providerListItems() {
              List<DataRow> providerList = [];
              for (var i = 0; i < providers.length; i++) {
                bool status = providers[i].status;
                DataRow dataRow =
                    DataRow(onSelectChanged: (bool? newVal) {}, cells: [
                  DataCell(Container(
                    width: 30,
                    child: Text((i + 1).toString()),
                  )),
                  DataCell(Container(
                    width: 120,
                    child: Text(providers[i].name),
                  )),
                  DataCell(Container(
                    width: 200,
                    child: Text(providers[i].address),
                  )),
                  DataCell(
                    Container(
                        width: 120, child: Text(providers[i].contact)),
                  ),
                  DataCell(
                    Container(width: 100, child: Text(providers[i].phone)),
                  ),
                  DataCell(
                    MouseRegion(
                      cursor: SystemMouseCursors.click,
                      child: Container(
                        alignment: Alignment.center,
                        width: 42,
                        height: 22,
                        child: FlutterSwitch(
                          value: providers[i].status,
                          width: 40.0,
                          height: 20.0,
                          borderRadius: 30.0,
                          onToggle: (val) {
                            setState(() {
                              showDialog(
                                  context: context,
                                  builder: (context) => AlertDialog(
                                        title: const CustomText(
                                          text: "Warning",
                                          weight: FontWeight.bold,
                                          size: 22,
                                        ),
                                        content: const CustomText(
                                          text:
                                              "Are you sure you want to change the status?",
                                          size: 16,
                                        ),
                                        actions: [
                                          TextButton(
                                            onPressed: () {
                                              status = val;
                                              providerDB
                                                  .doc(providers[i].id)
                                                  .update(
                                                      {'p_status': status});
                                              Navigator.pop(context);
                                            },
                                            child: const CustomText(
                                                text: 'Yes'),
                                          ),
                                          TextButton(
                                            onPressed: () {
                                              Navigator.pop(context);
                                            },
                                            child: const CustomText(
                                                text: 'No'),
                                          )
                                        ],
                                      ));
                            });
                          },
                        ),
                      ),
                    ),
                  ),
                  DataCell(Row(
                    children: [
                      SizedBox(
                        width: 30,
                        child: MaterialButton(
                          padding: const EdgeInsets.all(7.0),
                          onPressed: () {
                            menuController
                                .changeActiveitemTo(EditingPageName);
                            Navigator.push(
                              context,
                              MaterialPageRoute(
                                builder: (context) => ProviderEditPage(
                                    existing: providers[i]),
                              ),
                            );
                          },
                          child: const Icon(
                            Icons.edit,
                            color: Colors.black,
                            size: 18,
                          ),
                        ),
                      ),
                      const SizedBox(
                        width: 5,
                      ),
                      SizedBox(
                        width: 30,
                        child: MaterialButton(
                          padding: const EdgeInsets.all(7.0),
                          onPressed: () {
                            showDialog(
                                context: context,
                                builder: (context) => AlertDialog(
                                      title: const CustomText(
                                        text: "Delete Notice",
                                        weight: FontWeight.bold,
                                        size: 22,
                                      ),
                                      content: const CustomText(
                                        text:
                                            "Are you sure you want to delete the item?",
                                        size: 16,
                                      ),
                                      actions: [
                                        TextButton(
                                          onPressed: () {
                                            providerDB
                                                .doc(providers[i]
                                                    .id
                                                    .toString())
                                                .delete();
                                            Navigator.pop(context);
                                          },
                                          child:
                                              const CustomText(text: 'Yes'),
                                        ),
                                        TextButton(
                                          onPressed: () {
                                            Navigator.pop(context);
                                          },
                                          child:
                                              const CustomText(text: 'No'),
                                        )
                                      ],
                                    ));
                          },
                          child: const Icon(
                            Icons.delete,
                            color: Colors.black,
                            size: 18,
                          ),
                        ),
                      ),
                    ],
                  ))
                ]);
                providerList.add(dataRow);
              }
              return providerList;
            }

            return Container(
              height: 550,
              padding: const EdgeInsets.all(16),
              margin: const EdgeInsets.only(bottom: 30),
              decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(8),
                  boxShadow: [
                    BoxShadow(
                        offset: const Offset(0, 6),
                        color: lightGrey.withOpacity(.1),
                        blurRadius: 12),
                  ],
                  border: Border.all(color: lightGrey, width: .5)),
              child: DataTable(
                columnSpacing: 0,
                showCheckboxColumn: false,
                columns: const [
                  DataColumn(
                    label: CustomText(
                      text: "No.",
                      weight: FontWeight.bold,
                    ),
                  ),
                  DataColumn(
                    label: CustomText(
                      text: "Name",
                      weight: FontWeight.bold,
                    ),
                  ),
                  DataColumn(
                    label: CustomText(
                      text: "Address",
                      weight: FontWeight.bold,
                    ),
                  ),
                  DataColumn(
                    label: CustomText(
                      text: "Contact Person",
                      weight: FontWeight.bold,
                    ),
                  ),
                  DataColumn(
                    label: CustomText(
                      text: "Phone Number",
                      weight: FontWeight.bold,
                    ),
                  ),
                  DataColumn(
                    label: CustomText(
                      text: "Status",
                      weight: FontWeight.bold,
                    ),
                  ),
                  DataColumn(
                    label: CustomText(
                      text: "Tools",
                      weight: FontWeight.bold,
                    ),
                  ),
                ],
                rows: providerListItems(),
              ),
            );
          } else {
            return const Text('No data...');
          }
        case ConnectionState.done:
          return const Text('Something went wrong');
        default:
          return const Text('Something went wrong');
      }
    });

}

Is there something that I did wrong? Or is it a bug? Is the problem like I thought it is that data is not updated, so stream builder only builds once? If so, can you provide a solution for me, I can't seem to find the solution... Thank you!

--> EDIT

Initialization of providerDBsnaps

@override
void initState() {
  super.initState();
  providerDBsnaps = providerDB.snapshots();
  providerDBsnaps.listen((snapshot) {
  if (snapshot.docs.isNotEmpty) {
    final List<DocumentSnapshot> documents = snapshot.docs;

    providers = documents
        .map((e) => ProviderList.fromMap(e.data() as Map<String, dynamic>))
        .toList();
  }
});
}

Upvotes: 1

Views: 1824

Answers (2)

Bob Dozzo
Bob Dozzo

Reputation: 1

2 years late but for anyone who is interested or stuck like i was for 2 days straight,,, step 1.-get your data from your stream, step 2.-store that data in a local variable(make sure it can be updated with the stream whenever data changes,!important). step 3.-use a valuable listener in the UI ValueListenableBuilder<> Dont forget to have your provider set up on higher level.

Happy Coding Ladies and Gentleman!!!peace

Upvotes: 0

Saichi Okuma
Saichi Okuma

Reputation: 254

The connection state will remain in "Waiting" until a new snapshot is sent.

One way to solve it is to have your StreamBuilder inside a StatefullWidget so you can keep the state of "providers".

The function "providerListItems" could be outside the StreamBuilder, in your State class.

Then your FutureBuilder could be something like:

builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
  switch (snapshot.connectionState) {
    case ConnectionState.none:
    case ConnectionState.done:
      return const Text('Something went wrong');
    case ConnectionState.active:
      if (snapshot.hasData) {
        final List<DocumentSnapshot> documents = snapshot.data!.docs;
        providers = documents
            .map((e) =>
                ProviderList.fromMap(e.data() as Map<String, dynamic>))
            .toList();
      } else {
        providers = [];
      }
      break;
    default:
      break;
  }
  if(providers.isNotEmpty) {
    return Container(
      ...
      );
  } else {
    return const Text('No data...');
  }

Another way, in my opinion easier, would be to listen to your stream inside the InitState and update the providers when an event is sent.

class _MyWidget extends State<MyWidget> {
  List providers = [];

  @override
  void initState() {
    providerDBsnaps.listen((snapshot) {
      if (snapshot.hasData) {
        final List<DocumentSnapshot> documents = snapshot.data!.docs;
        providers = documents
                .map((e) =>
                    ProviderList.fromMap(e.data() as Map<String, dynamic>))
                .toList();
       } else {
            providers = [];
       }
       setState((){});
    };
  }
  
  @override
  Widget build(BuildContext context) {
      if(providers.isNotEmpty) {
        return Container(
          ...
          );
      } else {
        return const Text('No data...');
      }
  }

  List<DataRow> providerListItems() {
    final List<DataRow> providerList = [];
    ...
    return providerList;
  }

Upvotes: 0

Related Questions