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)