How can I fetch the Flutter Provider state from an rest API once after login?

349

I suggest you move from futurebuilder to streambuilder then try your provider. I don't like future builder because everytime the widget state changes, the future builder is invoked. The traffic to the server increases. The provider follows the syntax: Provider.of<your_class>(context,listen=false).mymethod. I keep listen=false because I am not doing any broadcast notifications using the provider.

Look at streambuilder and bloc event and data patterns

Share:
349
Kin Pu
Author by

Kin Pu

Updated on November 27, 2022

Comments

  • Kin Pu
    Kin Pu over 1 year

    I would like to know what is the best way to fetch all the Provider state that I need from an API. I am not doing it on my main screen after login because the initState() always will be called when I navigate back from another screen.

    Right now, my approach was creating a LoadScreen widget that is only built after login. There I use FutureBuilder to load the state I need and then Navigate to Main. This approach generates an exception but "works". What would be a correct approach?

    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    import 'package:shop/providers/auth.dart';
    import 'package:shop/providers/collections.dart';
    import 'package:shop/providers/customers.dart';
    import 'package:shop/providers/orders.dart';
    import 'package:shop/providers/products.dart';
    import 'package:shop/providers/recommended_categories.dart';
    import 'package:shop/providers/user_data.dart';
    import 'package:shop/screens/main_screen.dart';
    
    class LoadScreen extends StatelessWidget {
      static const routeName = '/load-screen'; //It shouldn't never be used.
    
      @override
      Widget build(BuildContext context) {
        Future<bool> loadState() async {
          //Loading all state data from firebase
          print('Loading States');
          try {
            final authData =
                await Provider.of<Auth>(context, listen: false).authData;
            final futures = <Future<dynamic>>[
              Provider.of<Products>(context, listen: false)
                  .fetchAndSet(authData), //Products
              Provider.of<RecommendedCategories>(context, listen: false)
                  .fetchAndSet(authData),
              Provider.of<Customers>(context, listen: false).fetchAndSet(authData),
              Provider.of<Orders>(context, listen: false).fetchAndSet(authData),
              Provider.of<UserData>(context, listen: false).fetchAndSet(authData),
              Provider.of<Collections>(context, listen: false)
                  .fetchAndSet(authData),
            ];
    
            print('Created futures list');
            final results = await Future.wait(futures);
            print(results);
          } catch (error, stacktrace) {
            print(error);
            print(stacktrace);
            throw error;
          }
    
          return true;
        }
    
        return Scaffold(
          body: FutureBuilder(
            future: loadState(),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                if (snapshot.hasError) {
                  return Center(
                    child: Text('${snapshot.error}'),
                  );
                }
                Navigator.of(context).pushNamed(MainScreen.routeName);
                return Center(
                  child: Text('Loading...'),
                );
              } else {
                return Center(
                  child: Text('Loading...'),
                );
              }
            },
          ),
        );
      }
    }
    

    My main is

    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    import 'package:shop/providers/collections.dart';
    import 'package:shop/providers/customers.dart';
    import 'package:shop/providers/products.dart';
    import 'package:shop/providers/recommended_categories.dart';
    import 'package:shop/providers/user_data.dart';
    import 'package:shop/screens/add_collection_screen.dart';
    import 'package:shop/screens/add_order_screen.dart';
    import 'package:shop/screens/load_screen.dart';
    import 'package:shop/screens/products_screen.dart';
    import 'package:shop/screens/customer_order_screen.dart';
    import 'package:shop/screens/edit_customer_screen.dart';
    import 'package:shop/screens/main_screen.dart';
    
    //import './screens/products_overview_screen.dart';
    import 'screens/cart_screen.dart';
    import './screens/product_detail_screen.dart';
    
    import './providers/cart.dart';
    import './providers/orders.dart';
    import './providers/auth.dart';
    
    import './screens/orders_screen.dart';
    import './screens/auth_screen.dart';
    import './screens/splash-screen.dart';
    import './screens/collections_screen.dart';
    // import './screens/collection_products_screen.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        print('MyApp.build');
        return MultiProvider(
          providers: [
            ChangeNotifierProvider(
              //Recommended approach when a new object is provided.
              create: (ctx) => Auth(),
            ),
            ChangeNotifierProvider<Products>(
              //Recommended approach when a new object is provided.
              create: (ctx) => Products(),
            ),
            ChangeNotifierProvider(
              create: (ctx) => Cart(),
            ),
            ChangeNotifierProvider<Orders>(
              create: (ctx) => Orders(),
            ),
            ChangeNotifierProvider(
              create: (ctx) => Customers(),
            ),
            ChangeNotifierProvider(
              create: (ctx) => RecommendedCategories(),
            ),
            ChangeNotifierProvider(
              create: (ctx) => UserData(),
            ),
            ChangeNotifierProvider(
              create: (ctx) => Collections(),
            ),
          ],
          child: Consumer<Auth>(
            builder: (ctx, auth, _) => MaterialApp(
              title: 'Shop',
              theme: ThemeData(
                primarySwatch: Colors.deepOrange,
                accentColor: Colors.purple,
                fontFamily: 'Lato',
              ),
              home: auth.canAuth
                  ? LoadScreen()
                  : FutureBuilder(
                      future: auth.tryAutoLogin(),
                      builder: (ctx, authResultSnapshot) =>
                          authResultSnapshot.connectionState ==
                                  ConnectionState.waiting
                              ? SplashScreen()
                              : AuthScreen(),
                    ),
              routes: {
                ProductDetailScreen.routeName: (ctx) => ProductDetailScreen(),
                CartScreen.routeName: (ctx) => CartScreen(),
                OrdersScreen.routeName: (ctx) => OrdersScreen(),
                CollectionProductsScreen.routeName: (ctx) =>
                    CollectionProductsScreen(),
                CollectionsScreen.routeName: (ctx) => CollectionsScreen(false),
                EditCustomerScreen.routeName: (ctx) => EditCustomerScreen(),
                CustomerOrderScreen.routeName: (ctx) => CustomerOrderScreen(),
                AddOrderScreen.routeName: (ctx) => AddOrderScreen(),
                AddCollectionScreen.routeName: (ctx) => AddCollectionScreen(),
                LoadScreen.routeName: (ctx) => LoadScreen(),
                MainScreen.routeName: (ctx) => MainScreen(),
              },
            ),
          ),
        );
      }
    }
    
    

    I am getting the following exception

    Exception caught by widgets library ═══════════════════════════════════
    The following assertion was thrown building FutureBuilder<bool>(dirty, state: _FutureBuilderState<bool>#547fc):
    setState() or markNeedsBuild() called during build.
    
    This Overlay 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: Overlay-[LabeledGlobalKey<OverlayState>#68f4a]
        state: OverlayState#77296(tickers: tracking 0 tickers, entries: [OverlayEntry#847ca(opaque: true; maintainState: false), OverlayEntry#7a334(opaque: false; maintainState: true), OverlayEntry#9e3e0(opaque: false; maintainState: false), OverlayEntry#99d1e(opaque: false; maintainState: true)])
    The widget which was currently being built when the offending call was made was: FutureBuilder<bool>
        dirty
        state: _FutureBuilderState<bool>#547fc
    The relevant error-causing widget was
    FutureBuilder<bool>
    lib/screens/load_screen.dart:48
    When the exception was thrown, this was the stack
    #0      Element.markNeedsBuild.<anonymous closure>
    package:flutter/…/widgets/framework.dart:4138
    #1      Element.markNeedsBuild
    package:flutter/…/widgets/framework.dart:4153
    #2      State.setState
    package:flutter/…/widgets/framework.dart:1287
    #3      OverlayState.rearrange
    package:flutter/…/widgets/overlay.dart:436
    #4      NavigatorState._flushHistoryUpdates
    package:flutter/…/widgets/navigator.dart:4043
    ...
    ════════════════════════════════════════════════════════════════════════════════
    E/flutter ( 5098): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: 'package:flutter/src/widgets/navigator.dart': Failed assertion: line 2997 pos 18: '!navigator._debugLocked': is not true.
    E/flutter ( 5098): #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:46:39)
    E/flutter ( 5098): #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:36:5)
    E/flutter ( 5098): #2      _RouteEntry.handlePush.<anonymous closure>
    package:flutter/…/widgets/navigator.dart:2997
    E/flutter ( 5098): #3      TickerFuture.whenCompleteOrCancel.thunk
    package:flutter/…/scheduler/ticker.dart:407
    E/flutter ( 5098): #4      _rootRunUnary (dart:async/zone.dart:1362:47)
    E/flutter ( 5098): #5      _CustomZone.runUnary (dart:async/zone.dart:1265:19)
    E/flutter ( 5098): #6      _FutureListener.handleValue (dart:async/future_impl.dart:152:18)
    E/flutter ( 5098): #7      Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:704:45)
    E/flutter ( 5098): #8      Future._propagateToListeners (dart:async/future_impl.dart:733:32)
    E/flutter ( 5098): #9      Future._completeWithValue (dart:async/future_impl.dart:539:5)
    E/flutter ( 5098): #10     Future._asyncCompleteWithValue.<anonymous closure> (dart:async/future_impl.dart:577:7)
    E/flutter ( 5098): #11     _rootRun (dart:async/zone.dart:1354:13)
    E/flutter ( 5098): #12     _CustomZone.run (dart:async/zone.dart:1258:19)
    E/flutter ( 5098): #13     _CustomZone.runGuarded (dart:async/zone.dart:1162:7)
    E/flutter ( 5098): #14     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1202:23)
    E/flutter ( 5098): #15     _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
    E/flutter ( 5098): #16     _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)