0

I have the following problem. When my app starts (user loggs in) I need to read value from firebase which is accountId - this is id of an account created separately from user account, which is stored in one of the Firestore documents. After fetching accountId through FutureBuilder which is nested in main.dart and I am saving it through

Provider.of<RegistrationHelper>(context.updateAccountId(accountId);

to my class RegistrationHelper to make it available for other places.

The thing is, that although the accountId gets saved in RegistrationHelper, I am getting the following error, you can find at the bottom. Here is a code of my main.dart. Does anyone know how to solve this problem? Many thanks in advance for your support!

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(JustAnApp());
}

class EmotionsApp extends StatelessWidget {
  final Future<FirebaseApp> _initialization = Firebase.initializeApp();

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<RegistrationHelper>(
          create: (_) => RegistrationHelper(),
        ),
        ChangeNotifierProvider<EmotionsHelper>(
          create: (_) => EmotionsHelper(),
        ),
      ],
      child: MaterialApp(
        title: 'Jak się dziś czujesz?',
        theme: ThemeData(
          primarySwatch: Colors.purple,
          accentColor: Colors.orange,
          accentColorBrightness: Brightness.light,
          canvasColor: Color.fromRGBO(255, 254, 229, 1),
          backgroundColor: Colors.deepPurple,
          buttonTheme: ButtonTheme.of(context).copyWith(
            buttonColor: Colors.purple,
            textTheme: ButtonTextTheme.primary,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(20),
            ),
          ),
          fontFamily: 'Raleway',
          textTheme: ThemeData.light().textTheme.copyWith(
                bodyText1: TextStyle(
                  color: Color.fromRGBO(20, 51, 51, 1),
                ),
                bodyText2: TextStyle(
                  color: Color.fromRGBO(20, 51, 51, 1),
                ),
                headline1: TextStyle(
                  fontSize: 20,
                  fontFamily: 'RobotoCondensed',
                  fontWeight: FontWeight.bold,
                ),
              ),
        ),
        home: FutureBuilder(
          future: _initialization,
          builder: (context, snapshot) {
            if (snapshot.hasError) {
              print('Snapshot error (main.dart) : ${snapshot.error}');
              return SomethingWentWrong();
            }
            if (snapshot.connectionState == ConnectionState.waiting) {
              return Center(child: CircularProgressIndicator());
            }

            if (snapshot.connectionState == ConnectionState.done) {
              // print('Snapshot (main.dart) : ${snapshot.connectionState}');
              return StreamBuilder(
                  stream: FirebaseAuth.instance.authStateChanges(),
                  builder: (context, streamSnapshot) {
                    if (streamSnapshot.data == null) return LoginScreen();

                    if (streamSnapshot.connectionState ==
                        ConnectionState.waiting)
                      return Center(
                        child: CircularProgressIndicator(),
                      );

                    if (streamSnapshot.hasData) {
                      //print('MAIN.DART streamSnapshot data: $streamSnapshot');
                      final user = FirebaseAuth.instance.currentUser;

                      return FutureBuilder(
//HERE IS WHERE I AM FETCHING ACCOUNT ID
                          future: FirebaseFirestore.instance
                              .collection('root')
                              .doc('users')
                              .collection('userData')
                              .doc(user!.uid)
                              .get(),
                          builder: (BuildContext context, AsyncSnapshot snap) {
                            if (snap.data == null)
                              return Center(child: CircularProgressIndicator());

                            if (snap.connectionState == ConnectionState.waiting)
                              return Center(
                                child: CircularProgressIndicator(),
                              );

                            if (snap.hasData) {
//AND HERE I AM STORING ACCOUNT ID TO REGISTRATION HELPER CLASS

                              Provider.of<RegistrationHelper>(context)
                                  .updateActualAccountId(
                                      snap.data['accountId']);

                              //return Text('accountId updated');
                            }
                            return FacilitiesScreen();
                          });
                    } else {
                      return LoginScreen();
                    }
                  });
            }
            return Center(child: CircularProgressIndicator());
          },
        ),
        routes: {
          ...
        },
      ),
    );
  }
}

And a simple method in RegistrationHelper:

 void updateActualAccountId(String accountId) {
    actualAccountId = accountId;
    notifyListeners();
  }

Error I am getting:

    ======== Exception caught by foundation library ====================================================
The following assertion was thrown while dispatching notifications for RegistrationHelper:
setState() or markNeedsBuild() called during build.

This _InheritedProviderScope<RegistrationHelper> widget cannot be marked as needing to build because the framework is already in the process of building widgets.  A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: _InheritedProviderScope<RegistrationHelper>
  value: Instance of 'RegistrationHelper'
  listening to value
The widget which was currently being built when the offending call was made was: FutureBuilder<DocumentSnapshot<Map<String, dynamic>>>
  dirty
  state: _FutureBuilderState<DocumentSnapshot<Map<String, dynamic>>>#bdfb1
When the exception was thrown, this was the stack: 
#0      Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:4305:11)
#1      Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:4320:6)
#2      _InheritedProviderScopeElement.markNeedsNotifyDependents (package:provider/src/inherited_provider.dart:531:5)
#3      ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:308:24)
#4      RegistrationHelper.updateActualAccountId (package:emotions4_flutter/auth/auth_registration_helper.dart:42:5)
#5      _FacilitiesScreenState._updateAccountId (package:emotions4_flutter/screens/facilities_screen.dart:27:10)
#6      _FacilitiesScreenState.build.<anonymous closure> (package:emotions4_flutter/screens/facilities_screen.dart:101:25)
#7      _FutureBuilderState.build (package:flutter/src/widgets/async.dart:782:55)
#8      StatefulElement.build (package:flutter/src/widgets/framework.dart:4782:27)
#9      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4665:15)
#10     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4840:11)
#11     Element.rebuild (package:flutter/src/widgets/framework.dart:4355:5)
#12     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2620:33)
#13     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:882:21)
#14     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:319:5)
#15     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1143:15)
#16     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1080:9)
#17     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:996:5)
#21     _invoke (dart:ui/hooks.dart:166:10)
#22     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:270:5)
#23     _drawFrame (dart:ui/hooks.dart:129:31)
(elided 3 frames from dart:async)
The RegistrationHelper sending notification was: Instance of 'RegistrationHelper'
====================================================================================================
AturicLiqr
  • 49
  • 7

2 Answers2

0

Try using a stateful widget instead:

class EmotionsApp extends StatefulWidget {
  @override
  State<EmotionsApp> createState() => _EmotionsAppState();
}

class _EmotionsAppState extends State<EmotionsApp> {
  final Future<FirebaseApp> _initialization = Firebase.initializeApp();

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<RegistrationHelper>(
          create: (_) => RegistrationHelper(),
        ),
        ChangeNotifierProvider<EmotionsHelper>(
          create: (_) => EmotionsHelper(),
        ),
      ],
      child: MaterialApp(
        title: 'Jak się dziś czujesz?',
        theme: ThemeData(
          primarySwatch: Colors.purple,
          accentColor: Colors.orange,
          accentColorBrightness: Brightness.light,
          canvasColor: Color.fromRGBO(255, 254, 229, 1),
          backgroundColor: Colors.deepPurple,
          buttonTheme: ButtonTheme.of(context).copyWith(
            buttonColor: Colors.purple,
            textTheme: ButtonTextTheme.primary,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(20),
            ),
          ),
          fontFamily: 'Raleway',
          textTheme: ThemeData.light().textTheme.copyWith(
            bodyText1: TextStyle(
              color: Color.fromRGBO(20, 51, 51, 1),
            ),
            bodyText2: TextStyle(
              color: Color.fromRGBO(20, 51, 51, 1),
            ),
            headline1: TextStyle(
              fontSize: 20,
              fontFamily: 'RobotoCondensed',
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
        home: FutureBuilder(
          future: _initialization,
          builder: (context, snapshot) {
            if (snapshot.hasError) {
              print('Snapshot error (main.dart) : ${snapshot.error}');
              return SomethingWentWrong();
            }
            if (snapshot.connectionState == ConnectionState.waiting) {
              return Center(child: CircularProgressIndicator());
            }

            if (snapshot.connectionState == ConnectionState.done) {
              // print('Snapshot (main.dart) : ${snapshot.connectionState}');
              return StreamBuilder(
                  stream: FirebaseAuth.instance.authStateChanges(),
                  builder: (context, streamSnapshot) {
                    if (streamSnapshot.data == null) return LoginScreen();

                    if (streamSnapshot.connectionState ==
                        ConnectionState.waiting)
                      return Center(
                        child: CircularProgressIndicator(),
                      );

                    if (streamSnapshot.hasData) {
                      //print('MAIN.DART streamSnapshot data: $streamSnapshot');
                      final user = FirebaseAuth.instance.currentUser;

                      return FutureBuilder(
//HERE IS WHERE I AM FETCHING ACCOUNT ID
                          future: FirebaseFirestore.instance
                              .collection('root')
                              .doc('users')
                              .collection('userData')
                              .doc(user!.uid)
                              .get(),
                          builder: (BuildContext context, AsyncSnapshot snap) {
                            if (snap.data == null)
                              return Center(child: CircularProgressIndicator());

                            if (snap.connectionState == ConnectionState.waiting)
                              return Center(
                                child: CircularProgressIndicator(),
                              );

                            if (snap.hasData) {
//AND HERE I AM STORING ACCOUNT ID TO REGISTRATION HELPER CLASS

                              Provider.of<RegistrationHelper>(context)
                                  .updateActualAccountId(
                                  snap.data['accountId']);

                              //return Text('accountId updated');
                            }
                            return FacilitiesScreen();
                          });
                    } else {
                      return LoginScreen();
                    }
                  });
            }
            return Center(child: CircularProgressIndicator());
          },
        ),
        routes: {
          ...
        },
      ),
    );
  }
}

The issue with instantiating something (here _initialization) in a widget, is that a widget will be instantiated multiple times while its visible (each time its parent build is called basically). What you want is to create only one _initialization object while the widget is on the screen. To do so, you have to create it in the State of a StatefulWidget, because State is long lived and will therefore only be instantiated once.

Lulupointu
  • 3,364
  • 1
  • 12
  • 28
  • Hi, thanks for your input @Lulupointu. I wish it was as simple as changing stateless to stateful widget. Unfortunately, if you meant just changing to stateful widget, it didn't change anything. Still the same `setState() or markNeedsBuild() called during build.`and it still points the method: Provider.of(context).updateActualAccountId(snap.data['accountId']); in the error log. – AturicLiqr Oct 04 '21 at 18:23
  • Even when using `Provider.of(context).updateActualAccountId(snap.data['accountId']);` in `didChangeDependencies` ? If so does it happen if you wrap it in `WidgetBindings.instance.addPostFramCallback` ? – Lulupointu Oct 04 '21 at 18:25
  • sorry for a delay, I have had travelling and didn’t get a chance to get down to the computer. When I have put whole `FutureBuilder` including Provider.of.... method into didChangeDependencies (in state section), then I got another error: `[core/no-app] No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp()` – AturicLiqr Oct 11 '21 at 21:53
  • No worries. Your new error is different entirely, it's because you forgot to initialize firebase as explained here: https://stackoverflow.com/questions/63492211/no-firebase-app-default-has-been-created-call-firebase-initializeapp-in – Lulupointu Oct 12 '21 at 12:00
0

The solution which seems to be working is calling RegistrationHelper method from `didChangeDependencies' from a home screen file (not from main.dart), like this:

  @override
  void didChangeDependencies() {
    Provider.of<RegistrationHelper>(context, listen: false).getAccountId();
    super.didChangeDependencies();
  }

No errors anymore. Thanks a lot for your help!

AturicLiqr
  • 49
  • 7