Flutter - Unable to navigate away/show dialog when snapshot error occurs - flutter

I got this simple build function which should show a dialog when the snapshot has an error, but I'm unable to do so without getting the following error:
flutter: The following assertion was thrown building
FutureBuilder(dirty, dependencies: flutter:
[_LocalizationsScope-[GlobalKey#f4f3f], _InheritedTheme], state:
flutter: _FutureBuilderState#175f8): flutter:
setState() or markNeedsBuild() called during build. flutter: This
Overlay widget cannot be marked as needing to build because the
framework is already in the flutter: process of building widgets. A
widget can be marked as needing to be built during the build phase
flutter: only if one of its ancestors is currently building. This
exception is allowed because the framework flutter: builds parent
widgets before children, which means a dirty descendant will always be
built. flutter: Otherwise, the framework might not visit this widget
during this build phase. flutter: The widget on which setState() or
markNeedsBuild() was called was: flutter:
Overlay-[LabeledGlobalKey#07fd4]
Code:
showUnknownErrorAlertDialog(BuildContext context, [void Function() onClose]) {
showDialog(
context: context,
builder: (BuildContext builderContext) {
return AlertDialog(
title: Text(AppLocalizations.of(builderContext).lang['unknownError']),
actions: <Widget>[
FlatButton(
child: Text(AppLocalizations.of(builderContext).lang['close']),
onPressed: () {
Navigator.of(builderContext).pop();
if (onClose != null) {
onClose();
}
},
),
],
);
}
);
}
goBack() {
locator<AppNavigation>().navigateTo('gameOverview', AppGameArgs(gameName));
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getGameSession(),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return showUnknownErrorAlertDialog(context, () {
goBack();
});
}
/ ... omitted code .../
How am I supposed to handle errors in the build function if I'm not allowed to show a dialog or navigate away instantly when there's an error?

you can't return the dialog directly from the FuturBuilder.You have to use showDialog() to show any dialog and for the click event if you are using navigation in the dialog it'll be used as the navigation of the dialog not the page.
if(snapshot.hasError()){
showDialog(showUnknownErrorAlertDialog(context, () {
Navigator.of(context).pop(1);
})).then((value){
if(value=null && value==1)
goBack();
});
return Container();
}
you can return value from dialog and check the result and run your goBack() there

Related

How to rebuild a widget whenever the validation function is triggered in TextFormField Flutter?

I'm receiving the following error message:
══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞════════════════════════════════════════════════════════
The following assertion was thrown while dispatching notifications for ErrorTextNotifier:
setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<ValidityNotifier?> 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<ValidityNotifier?>
The widget which was currently being built when the offending call was made was:
This is my code (I've had to omit a bunch of the code)
TextFormField
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ValidityNotifier(),
child: Builder(builder: (context) {
return Column(
children: [
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (str) {
var validity = //custom validity function
Provider.of<ValidityNotifier>(context, listen: false).setValidity(validity);
return null;
},
),
Consumer<ValidityNotifier>(builder: (context, model, child) {
if (model.isValid) {
return Text("Custom Validity Message")
}
return const SizedBox.shrink();
}),
],
);
}),
);
}
}
class ValidityNotifier extends ChangeNotifier {
bool _isValid = false;
bool get isValid => _isValid;
void setValidity(bool isValid) {
_isValid=isValid;
notifyListeners();
}
}
I want the state to be changed every time the validation function is called, I've considered adding a listener to a TextEditingController but it would trigger a rebuild every time an input is made.
As the error message says you have concurrent builds running. When you call setValidity which triggers a rebuild due to notifyListeners, Flutter is already in the process of building the TextFormField.
You can use a post frame callback, in this case the setValidity method call will run only once the current build is finished.
Instead of:
Provider.of<ValidityNotifier>(context, listen: false).setValidity(validity);
Try this:
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Provider.of<ValidityNotifier>(context, listen: false)
.setValidity(validity);
});
Inside TextFormField
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) => value!.isEmpty ? "Your special string here" : null

how to call setState inside FutureBuilder in Flutter

I'm trying to set State based on result received by FutureBuilder, looks like it is not possible since FutureBuild is still building, any ideas please ? error :
The following assertion was thrown building FutureBuilder<String>(dirty, state: _FutureBuilderState<String>#db9f1):
setState() or markNeedsBuild() called during build.
This StatefulBuilder 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: StatefulBuilder
my code :
FutureBuilder<String>(
future: fetchIdPlayer(idPlayer, bouquet),
builder: (context, snapid) {
if (!snapid.hasData)
return Container(
height: mobileHeight * 0.05,
child: Center(
child: CircularProgressIndicator(),
),
);
else if (snapid.data == "Error_ID") {
setState(() {
have_ID = true;
resultName = "رقم تعريف اللاعب خاطئ";
});
}
})
You can workaround the error you are getting by scheduling the setState to be executed in the next frame and not potentially during build.
else if (snapid.data == "Error_ID") {
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
///This schedules the callback to be executed in the next frame
/// thus avoiding calling setState during build
setState(() {
have_ID = true;
resultName = "رقم تعريف اللاعب خاطئ";
});
});
...
You can just wrap the widget who will use resultName and have_ID with FutureBuilder .So there is no need to setState.you can also handle error as well .If you want to setState then use a asyn function and after result is fetched you can just call setState
You could use the property connectionState of snapid.
This should generally work as connectionState is set to ConnectionState.done whenever the future is terminated.
FutureBuilder<String>(
future: fetchIdPlayer(idPlayer, bouquet),
builder: (context, snapid) {
if (snapshot.connectionState == ConnectionState.done) {
setState((){
//...
})
}
if (!snapid.hasData)
//...
else if (snapid.data == "Error_ID") {
//...
}
})
The key bit of this error message is, "called during build". You need to wait until after build. This can be accomplished with either:
WidgetsBinding.instance?.addPostFrameCallback()
https://api.flutter.dev/flutter/widgets/WidgetsBinding-mixin.html
or
SchedulerBinding.instance?.addPostFrameCallback()
https://api.flutter.dev/flutter/scheduler/SchedulerBinding-mixin.html
Flutter prohibit this kind of situation because setting a state in side a future widget will cause to rebuild the future widget again and this will become an infinite loop. Try to separate logic from UI. Example credits goes to #pskink and #Mofidul Islam
In your state class create a wrapper
Future<String> wrapper(idPlayer, bouquet) async {
final foo = await fetchIdPlayer(idPlayer, bouquet);
//handle errors
if(foo.error){
have_ID = true;
resultName = "رقم تعريف اللاعب خاطئ";
}
return foo;
}
call the wrapper in your widget
FutureBuilder<String>(
future: wrapper(idPlayer, bouquet),
builder: (context, snapid) {
if (!snapid.hasData)
return Container(
height: mobileHeight * 0.05,
child: Center(
child: CircularProgressIndicator(),
),
);
else {
//UI to show error
}
})

"Unhandled Exception: setState() or markNeedsBuild() called during build" when calling Flutter's showDialog()

I encountered with the error described in the title, and later found the culprit, which was a call of showDialog() inside a StreamBuilder.
TL;DR Solution
If calling showDialog()inside StreamBuilder, deleting it might solve the problem.
The code is sampled below:
class SomeClass extends StatefulWidget {
final String restaurantId;
#override
_HomePageState createState() => _HomePageState();
}
class _SomeClassState extends State<SomeClass> {
...
#override
Widget build(BuildContext context) {
...
return StreamBuilder<T>(
stream: _stream,
builder: (context, snapshot) {
// Wait for connection to stablish.
if (snapshot.connectionState == ConnectionState.waiting)
Dialogs.showLoadingDialog(context, _keyLoader);
// Validate there's snapshot data; else, return error.
if (snapshot.hasData) {
... *Build some Scaffold*
} else {
... *Build some Error Message*
}
}
);
}
class Dialogs {
static Future<void> showLoadingDialog(
BuildContext context, GlobalKey key) async {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return new WillPopScope(
onWillPop: () async => false,
child: Container(
key: key,
child: ...
),
);
},
);
}
}
Why is that, and how can I be able to call showDialog() properly? I found that by deleting the call to showDialog() inside Dialogs.showLoadingDialog() completely solved the problem.
This is the complete error output:
Unhandled Exception: 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>#c2441]
The widget which was currently being built when the offending call was made was:
StreamBuilder<Restaurant>
#0 Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:4292:11)
#1 Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:4307:6)
#2 State.setState (packag<…>
[VERBOSE-2:ui_dart_state.cc(177)] Unhandled Exception: 'package:flutter/src/widgets/navigator.dart': Failed assertion: line 2416 pos 18: '!navigator._debugLocked': is not true.
...
You need to wait for the widget to build and then call showDialog.
WidgetsBinding.instance.addPostFrameCallback((_) {
// show dialog here
}
I think you need to return a widget from your builder function.
Dialogs.showLoadingDialog(context, _keyLoader);
you need to add 'return' befor this.
Before UI Build we can not call setState. WidgetsBinding.instance.addPostFrameCallback is method which give call back when build is complted.

"setState() or markNeedsBuild() called during build" error trying to push a replacement in Navigator inside a Consumer widget (provider package)

This week I've began developing in flutter and i'm not able to solve this problem.
I'm building a login page that calls an API to login and after redirects to an homepage.
This is the exception generated by Navigator.pushReplacement in the first code bloc.
In that moment apiCall.isFetching is false cause fetching ended and apiCall.response contains the required data.
Exception details:
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building Consumer<ApiCallChangeNotifier>(dirty, dependencies: [InheritedProvider<ApiCallChangeNotifier>]):
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>#4dc85]
state: OverlayState#bd97e(tickers: tracking 1 ticker, entries: [OverlayEntry#2941b(opaque: false; maintainState: false), OverlayEntry#37814(opaque: false; maintainState: true), OverlayEntry#f92c0(opaque: false; maintainState: false), OverlayEntry#da26d(opaque: false; maintainState: true)])
The widget which was currently being built when the offending call was made was: Consumer<ApiCallChangeNotifier>
dirty
dependencies: [InheritedProvider<ApiCallChangeNotifier>]
User-created ancestor of the error-causing widget was:
Expanded file:///C:/flutter_test/lib/screens/login/LoginScreen.dart:153:37
When the exception was thrown, this was the stack:
#0 Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:3687:11)
#1 Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:3702:6)
#2 State.setState (package:flutter/src/widgets/framework.dart:1161:14)
#3 OverlayState.insertAll (package:flutter/src/widgets/overlay.dart:346:5)
#4 OverlayRoute.install (package:flutter/src/widgets/routes.dart:43:24)
...
Here is my function to create the login button, it's called from build function of LoginScreen (StatelessWidget)
Widget loginButton(BuildContext context) {
return Consumer<ApiCallChangeNotifier>(
builder: (context, apiCall, child) => apiCall.isFetching
? CircularProgressIndicator()
: apiCall.response != null
? Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => HomeScreen(
(apiCall.response as LoginResponse).email)))
: RaisedButton(
...
onPressed: () {
attemptLogin(context);
},
...
));
}
The attemptLogin funtion:
void attemptLogin(BuildContext context) {
Provider.of<ApiCallChangeNotifier>(context, listen: false).callApi(
MyApiServices().attemptLogin,
{
'email': emailController.value.text,
'password': passwordController.value.text,
},
urlController.value.text
);
}
ApiCallChangeNotifier
class ApiCallChangeNotifier extends ChangeNotifier {
bool isFetching = false;
Object response;
Future<LoginResponse> callApi(apiFunction, bodyParams, customUrl) async {
isFetching = true;
notifyListeners();
response = await apiFunction(bodyParams, customUrl);
isFetching = false;
notifyListeners();
return response;
}
}
MyApiServices.attemptLogin is a function that handles the API call and returns an Object LoginResponse
Hope I've given enough info!
For me, It was when I use navigator before the build finished!
Just put your navigation code in here:
WidgetsBinding.instance.addPostFrameCallback((_) {
// Do everything you want here...
});
Instead of trying to push the new route from LoginResponse Consumer I modified attemptLogin() to wait the result and to navigate to the new route!
void attemptLogin(BuildContext context) async {
LoginResponse _apiResponse =
await Provider.of<ApiCallChangeNotifier>(context, listen: false)
.callApi(
MyApiServices().attemptLogin,
{
'email': emailController.value.text,
'password': passwordController.value.text,
},
urlController.value.text);
if (_apiResponse != null) {
if (_apiResponse.email != null) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => HomeScreen(_apiResponse.email)));
} else if (_apiResponse.errorMessage != null) {
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text(_apiResponse.errorMessage)));
} else {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(KanbanBOXApi().unknownErrorMessage)));
}
}
}

Flutter BLoC: Navigator.pop in StreamBuilder in build() method

I'm following BLoC pattern and subscribing to stream, and reacting to state changes in build method. When data is loaded I want to close the screen.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Bloc'),
),
body: SafeArea(
child: StreamBuilder<UserState>(
stream: _userBloc.user,
initialData: UserInitState(),
builder: (context, snapshot) {
if (snapshot.data is UserInitState) {
return _buildInit();
}
if (snapshot.data is UserDataState) {
Navigator.pop(context, true);
return Container();
}
if (snapshot.data is UserLoadingState) {
return _buildLoading();
}
},
),
),
);
}
When I do Navigator.pop(context, true); in build() method I get:
I/flutter ( 4360): ══╡ EXCEPTION CAUGHT BY ANIMATION LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter ( 4360): The following assertion was thrown while notifying status listeners for AnimationController:
I/flutter ( 4360): setState() or markNeedsBuild() called during build.
I/flutter ( 4360): This Overlay widget cannot be marked as needing to build because the framework is already in the
I/flutter ( 4360): process of building widgets. A widget can be marked as needing to be built during the build phase
I/flutter ( 4360): only if one of its ancestors is currently building. This exception is allowed because the framework
I/flutter ( 4360): builds parent widgets before children, which means a dirty descendant will always be built.
I/flutter ( 4360): Otherwise, the framework might not visit this widget during this build phase.
What is the right way to handle such cases in BLoC pattern?
On of the solutions I come up with is to start listening to stream on initState(). In this case I need to broadcast() my stream because I have 2 subscribers.
Are there any better solutions for this?
I think I have got a solution for you. (Please check it)
Make your code look like :
Widget build(BuildContext context) {
// other stuff
if (snapshot.data is UserDataState) {
myCallback(() {
Navigator.pop(context, true);
});
}
// other stuff
}
// after build method (but still in the same class,...) write below method
void myCallback(Function callback) {
WidgetsBinding.instance.addPostFrameCallback((_) {
callback();
});
}
Hope it helps. Just try it and please report here to help others too!
Source (flutter_bloc Login medium article)
Description
bool hasPop = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Bloc'),
),
body: SafeArea(
child: StreamBuilder<UserState>(
stream: _userBloc.user,
initialData: UserInitState(),
builder: (context, snapshot) {
if (snapshot.data is UserInitState) {
return _buildInit();
}
if (snapshot.data is UserDataState) {
if(!hasPop){
hasPop = true;
Navigator.pop(context, true);
}
return Container();
}
if (snapshot.data is UserLoadingState) {
return _buildLoading();
}
},
),
),
);
} ```
I could imagine three possible solutions:
1) It looks to me like it would be best to restructure your widgets. As far as I can see you want a "Loading Screen" .. I see no reason that this has to be it's own navigation item, instead of just another widget.
Ie. You could push the StreamBuilder one? level up.. so your builder method looks like:
if (!snapshot.hasData) {
return LoadingScreen(snapshot);
}
// Whatever you do with your data.
2) I think i personally would create a StatefulWidget, listen to the stream manually in initState() and call setState() manually. No need for a StreamBuilder
3) as a crazy workaround you could probably use Future(() { Navigator.of(context).pop(); }); in your builder. (it's possible that you'd have to use the context of the build method, not the builder. but I wouldn't recommend that solution anyway)