Provider rebuilds the widget, but nothing shows up until a "Hot restart"

Issue

I am building a flutter app and I get some data from a future, I also got the same data with a changenotifier. Well the logic is that while some object doesn’t have data because its waiting on the future then display a spinning circle. I have already done this in the app and I have a widget called Loading() when the object has not received data. The problem I have run into is that I get the data, but it doesn’t display anything.

Here is what it should display and after performing a hot restart is the result

the data displays correctly until I perform a hot refresh of the app. a capital R instead of a lowercase r. The difference is that it starts the app and deletes all aggregated data.

when this happens it seems that the data fills the object but I hypothesize that it is becoming not null meaning [] which is empty but not null and is displaying the data "too quickly" this in turn displays nothing for this widget until I restart "r" which shows me the above screenshot.

Shows what is happening when a hot refresh is done "R"

here is the offending code.

import 'package:disc_t/Screens/LoggedIn/Classes/classTile.dart';
import 'package:disc_t/Screens/LoggedIn/Classes/classpage.dart';
import 'package:disc_t/Screens/LoggedIn/Classes/classpageroute.dart';
import 'package:disc_t/Services/database.dart';
import 'package:disc_t/models/user.dart';
import 'package:disc_t/shared/loading.dart';
import 'package:flutter/material.dart';
import 'package:morpheus/page_routes/morpheus_page_route.dart';
import 'package:provider/provider.dart';

class ClassList extends StatefulWidget {
  @override
  _ClassListState createState() => _ClassListState();
}

class _ClassListState extends State<ClassList> {
  @override
  void initState() {
    ClassDataNotifier classdatanotif =
        Provider.of<ClassDataNotifier>(context, listen: false);
    // final user = Provider.of<User>(context);
    // getTheClasses(classdatanotif);
    // List<ClassData> d = classes;
  }

  @override
  Widget build(BuildContext context) {
    ClassDataNotifier classdatanotif = Provider.of<ClassDataNotifier>(context);

    List<ClassData> cData = Provider.of<List<ClassData>>(context);



    bool rebd = false;

    Widget checker(bool r) {
      if (cData == null) {
        return Loading();
      } else {
        if (rebd == false) {
          setState(() {
            rebd = true;
          });

          rebd = true;

          return checker(rebd);

          // return Text("Still Loading");
        } else {
          return PageView.builder(
              scrollDirection: Axis.horizontal,
              itemCount: cData.length,
              // controller: PageController(viewportFraction: 0.8),
              itemBuilder: (context, index) {
                return Hero(
                  tag: cData[index],
                  child: GestureDetector(
                    onTap: () {
                      // Navigator.of(context).push(ClassPageRoute(cData[index]));
                      Navigator.push(
                          context,
                          MorpheusPageRoute(
                              builder: (context) =>
                                  ClassPage(data: cData[index]),
                              transitionToChild: true));
                    },
                    child: ClassTile(
                      classname: cData[index].classname,
                      description: cData[index].classdescription,
                      classcode: cData[index].documentID,
                    ),
                  ),
                );
              });
        }
      }
    }

    
    return checker(rebd);
  }
}

here is how the provider is implemented

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.

  // final DatabaseService ds = DatabaseService();
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        StreamProvider<User>.value(
          value: AuthService().user,
          // child: MaterialApp(
          //   home: Wrapper(),
          // ),
        ),
        ChangeNotifierProvider<ClassDataNotifier>(
          create: (context) => ClassDataNotifier(),
        ),
        FutureProvider(
          create: (context) => DatabaseService().fetchClassdata,
        )
      ],
      child: MaterialApp(home: Wrapper()),
    );
  }
}

and here is the function that is ran to get the data

Future<List<ClassData>> get fetchClassdata async {
    QuerySnapshot snapshot = await classesCollection.getDocuments();

    List<ClassData> _classList = List<ClassData>();

    snapshot.documents.forEach((element) async {
      QuerySnapshot pre = await Firestore.instance
          .collection("Classes")
          .document(element.documentID)
          .collection("Pre")
          .getDocuments();

      List<Preq> _preList = List<Preq>();

      pre.documents.forEach((preClass) {
        Preq preqData = Preq.fromMap(preClass.data);

        if (preClass.data != null) {
          _preList.add(preqData);
        }
      });

      ClassData data =
          ClassData.fromMap(element.data, element.documentID, _preList);

      if (data != null) {
        _classList.add(data);
      }
    });

    return _classList;
  }

Solution

I think the logic of your provider is fine, the problem lies in the line

snapshot.documents.forEach((element) async {
...
}

The forEach is not a Future (what is inside it’s a future because the async, but the method itself not) so the code runs the first time, it reaches the forEach which does its own future on each value and propagate to the next line of code, the return, but the list is empty because the forEach isn’t done yet.

There is a special Future.forEach for this case so you can wait for the value method before running the next line

Future<List<ClassData>> get fetchClassdata async {
QuerySnapshot snapshot = await classesCollection.getDocuments();

List<ClassData> _classList = List<ClassData>();

await Future.forEach(snapshot.documents, (element) async {
  QuerySnapshot pre = await Firestore.instance
      .collection("Classes")
      .document(element.documentID)
      .collection("Pre")
      .getDocuments();

  List<Preq> _preList = List<Preq>();

  pre.documents.forEach((preClass) {
    Preq preqData = Preq.fromMap(preClass.data);

    if (preClass.data != null) {
      _preList.add(preqData);
    }
  });

  ClassData data =
      ClassData.fromMap(element.data, element.documentID, _preList);

  if (data != null) {
    _classList.add(data);
  }
});

return _classList;
}

Here is a similar problem with provider with a forEach. Maybe it can help you understand a bit better

Answered By – EdwynZN

Answer Checked By – Mary Flores (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *