If I throw an exception in a Future, and use such future in a FutureBuilder, the debugger warns me about it being "uncaught". Is this by design or am I using FutureBuilders wrong?
My scenario is a typical one: show a loading spinner while data is retrieved from an HTTP API. I do so through the following (simplified) code:
class MyWidgetState extends State<MyWidget> {
Future<String> getSomeText() async {
// Just throw an exception here to simulate a network error
throw Exception("Network error");
}
#override
Widget build(BuildContext context) => FutureBuilder(
future: getSomeText(),
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError)
return Text("Got an error");
return Text(snapshot.data);
}
else
return CircularProgressIndicator();
}
);
}
When I draw this widget, the Network error exception is thrown and my debugger (I use VSCode with the official Flutter plugin) halts execution and shows it to me. If I then chose to continue execution, the app behaves as expected.
Reading around a bit, I thought that happens because getSomeText() gets called and completes BEFORE the builder is even created, and the builder's exception handlers do not have time to register with the future. However, using Future.delay() to introduce a delay results in exactly the same behavior.
I also tried to execute such code in one of Flutter's documentation code boxes, and there it apparently works without throwing exceptions. Is it simply ignoring it as it is thrown into a Future?
I wonder if such exception should be caught (as I expect) or not? Is leaving it uncaught in this scenario a Flutter design choice?
Related
I am getting the following message when internet goes off.
E/flutter (26162): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: This widget has been unmounted, so the State no longer has a context (and should be considered defunct).
E/flutter (26162): Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.
It is showing the message from this section of my code.
#override
void initState() {
super.initState();
try {
InternetAddress.lookup('google.com').then((result) {
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
// internet conn available
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) =>
(Constants.prefsMobile.getString("mobile") == null
? Login()
// : SignupPayoutPassword(signupdata: [])),
: Home(signindata: signinData)),
));
} else {
// no conn
_showdialog();
}
}).catchError((error) {
// no conn
_showdialog();
});
} on SocketException catch (_) {
// no internet
_showdialog();
}
Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult connresult) {
if (connresult == ConnectivityResult.none) {
} else if (previous == ConnectivityResult.none) {
// internet conn
Navigator.of(context).pop();
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) =>
(Constants.prefsMobile.getString("mobile") == null
? Login()
: Home(signindata: signinData)),
));
}
previous = connresult;
});
}
I have not used any dispose method for this. If any one know please let me know how can I solve this problem. How to dispose. I am getting a crash report after my app close as follows
E/AndroidRuntime( 8064): java.lang.RuntimeException: Unable to destroy activity {com.example.aa_store/com.example.aa_store.MainActivity}: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter activity
is this crash message for the above problem? Please help.
Please use.
#override
void dispose() {
Connectivity().onConnectivityChanged.cancel();
super.dispose();
}
Better, define your stream outside the initState:
Stream _connectivityStream = Connectivity().onConnectivityChanged;
and in dispose use _connectivityStream.cancel();.
The error means that you instantiated a stream, which on changes of events, triggers build changes. This stream is setup during initState, meaning when the widget is first created. Connectivity().onConnectivityChanged.listen(....etc).
But you never tell flutter to cancel listening to this stream when the widget is disposed.
This is the role of the dispose method. Similar to how you want logic to be performed when the widget is built, you use initState, you should also tell it when you are no longer interested in these changes in logic.
Failing to do so, will result in the error you are having, aside from memory leaks also.
This is the translation of the error This widget has been unmounted, so the State no longer has a context (and should be considered defunct). which you posted. "Hey, this widget isn't in the tree anymore, its state is not mounted, I can't rebuild it, and you need to pay attention to it.
Please consider using the dispose method for these Flutter elements, not to mention all of them, but from the top of my mind:
AnimationControllers.
Timers.
Streams listeners.
Web application contains many screens with list views and edit dialogs for edit selected records. List view uses "standard display behavour" using StreamBuilder.
Simplified bloc.dart
class Bloc {
final _subject = StreamController<Data>.broadcast();
Stream<Data> get data => _subject.asyncMap(_getData);
Future<Data> _getData(...) {
try {
final data = await api.getUrl(...);
return data;
} on SomeException catch (e) {
// This exception is catched by StreamBuilder
throw ApiException('could not get data');
}
}
}
Simplified widget.dart
StreamBuilder<...>(
stream: bloc.data,
builder: (context, snapshot) {
if (snapshot.hasError) {
// Bloc throws an exception which thrown by API client (HttpClient).
return ErrorWidget(snapshot.error.toString());
}
if (snapshot.hasData) {
return ListView.builder(...);
} else {
return CircularProgressIndicator();
}
}
),
Each edit dialog uses showDialog to display selected item details and save modifications.
There may be a situation when (for example) access token is invalid (or expired) during his already authenticated session. It is necessary to inform user about this (showing him a warning) and after closing warning destroy the session and return to login screen. Is it possible to implement effective solution to catch specific exception (eg. TokenInvalidException) in a single place without adding symbolic code like:
if (snapshot.error is TokenInvalidException) {
// destroy session
// return to Login
}
into each StreamBuilder list view and some code to check if exception occurred on each Dialog's save action?
I have 100500 screens and I do not want to implement the same code for each screen. Actually I want some global exception catcher which catches the required exception and executes specific action.
Now I see some global StreamController with stream which accepts such exception (events) and is listen by some global parent widget (MaterialApp or maybe Main).
final globalExceptionController = StreamController<TokenInvalidException>.broadcast();
// somewhere
globalExceptionController.stream.listen((e) async {
// Show warning
final result = await showDialog(...);
// Go to login
Navigator.pushReplacement(context, <login page route>);
}) {}
The problem I see here is the bloc object which throws the exception will have strong dependency on stream controller which is not interested to it absolutely. But actually I need to link somehow the bloc and GUI because to show warning and switch the route I need a context.
Any ideas or critics are appreciated.
Maybe the coordinator bloc pattern described here helps you with this problem
I am using FutureBuilder inside my Build method and the FutureBuilder keeps firing up again and again. I only want to call FutureBuilder once until my future is loaded and then call another function as soon as it is done. This is my Build function -
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: parsings(),
builder:
(BuildContext context, AsyncSnapshot<dynamic> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return LoadingState(context);
default:
if (snapshot.hasError)
return new Text(
'Error in snapshot: \n${snapshot.error}');
else {
return func1();
}
}
},
);
}
As i said, my Build function keeps on building again and again.
Before this, i was using my future inside initState but because of that, my actual page that i wanted to return gave null values all over until the API was called. I dont want to see null values at all so i wanted to use FutureBuilder and display LoadingState() until the API was called and then show my page which has all the called values and doesnt show null values. Any help would be appreciated.
EDIT: The detailed issue i am facing is as the build function is being called again and again, i am seeing my LoadingState() again and again, that is, LoadingState() is appearing and then func1() is appearing then again, LoadingState(), then func1() and so on. this process does not stop at all. Also, i do not want to use parsings() in initState because on doing so, before the API is called, the func1() shows null values in my data and as soon as it is called, the build function changes its values to the values called from API. I don't want to see it like that which is why i wanted to use FutureBuilder.
I have a notification service which is a change notifier. When the service recieves a notification it notifies all listners. I want to display a dialog when the listners get notified. So I do the following in my build method
Consumer<NotificationService>(
builder: (BuildContext context, NotificationService notificationNotifier, _) {
if (notificationNotifier.hasNotifications)
_showNotification(context, notificationNotifier.popNotification());
return Scaffold(
This is the shownotification method
Future<dynamic> _showNotification(BuildContext context, NotificationModel notification) async {
try {
print(notification.title);
await PlatformAlertDialog(
notification.title,
notification.body,
).show(context);
} on UserFriendlyException catch (error) {
await PlatformAlertDialog(
error.title,
error.message,
).show(context);
}
}
So but this throws an error because I want to build the dialog while I am building Unhandled Exception: setState() or markNeedsBuild() called during build.
I do like using the change notifier provider. So how could I make this work?
You can use the SchedulerBinding.instance Api of Flutter to prevent this exception. The error happens because before the build method was built, you called a dialog that will prevent the reconstruction from finishing.
So there is no error:
Consumer<NotificationService>(
builder: (BuildContext context, NotificationService notificationNotifier, _) {
if (notificationNotifier.hasNotifications){
SchedulerBinding.instance.addPostFrameCallback((_) =>
_showNotification(context, notificationNotifier.popNotification()));
}
return Scaffold(
However, the Flutter documentation recommends that you do not perform functions within the build method. this can have side effects.
You are probably using this approach because of the context required by the dialog. I recommend taking a look at this plugin:
https://pub.dev/packages/get
With it you can open dialogs from anywhere in your code, without the need for context, and its state manager is easier than changeNotifier, but it's not that bad of performance.
According to the documentation, changeNotifier must be used on one, or a maximum of two listeners. His performance is very bad, and that of this plugin is very similar, but without using changeNotifier, I believe that this will make your project evolve a little more.
https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html
I want to populate my lists by making API calls when moving to a screen in flutter. I tried calling the async function in the init state however it takes time to load the data of course, and that leads to a null error.
I tried using a future builder in my view. However, I want to make multiple API calls and I don't think that is possible with one future builder. And I would like to avoid having different future builders for each list.
Is there a neat way to do this?
Also is it advisable to load data and pass it on from the previous screen.. since I would like to avoid the loading time?
current code
FutureBuilder(
future: getAllData,
initialData: [],
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data.isEmpty) return Center(child: CircularProgressIndicator(valueColor: new AlwaysStoppedAnimation<Color>(Colors.red),));
else {
return createNewTaskView(context, snapshot.data);
}
}),
init method
#override
void initState() {
this.getAllData = getSocieties();
}