I have a FutureBuilder widget which calls an async function that is meant to fetch a list of controls available for a given device from a remote server.
FutureBuilder<List<Control>> buildControlList() {
return FutureBuilder<List<Control>>(
future: widget.server.getResponse(widget.device, widget.controlSet),
builder:
(BuildContext context, AsyncSnapshot<List<Control>> snapshot) {
if (snapshot.hasData) {
return buildList(buildControls(widget.controlSet, snapshot.data));
} else if (snapshot.hasError) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('${snapshot.error.toString()}')));
}
return const Center(child: CircularProgressIndicator());
});
}
The getResponse function calls another async _get function which builds and performs the actual api request. Once a response is received, it send the response data to a synchronous function which checks the content for errors reported by the server. If it finds one, it throws its own error:
Future<List<Control>> getResponse(Device device, ControlSet controlSet,
{Map<String, dynamic> params}) async {
final String epoint = 'device/${device.name}/set/${controlSet.name}/';
final http.Response response = await _get(epoint, params: params);
_checkServerError(response); //synchronous check of response data
final List<Control> values =
json.decode(response.body).map<Control>((dynamic e) {
return Control.fromJson(e);
}).toList();
return values;
}
The _checkServerError function is pretty simple. I have tried both sync and async versions of this and seem to have the same behavior.
void _checkServerError(http.Response response) {
if (response.statusCode != 200) {
final Map<String, dynamic> body = json.decode(response.body);
if (body.containsKey('error')) {
throw ServerException(
statusCode: response.statusCode, errorMsg: body['error']);
}
}
}
I cannot seem to get the thrown ServerException to propagate up to the FutureBuilder's snapshot.hasError check. I don't want to have to catch the error and re-throw it in the getResponse() function though it does appear to work. Changing _checkServerError() to an async function and awaiting it does not seem to work either.
Why doesn't the error continue upwards to the FutureBuilder?
Edit: The call stack shows the error originating in _checkServerError() and propagating upwards to the FutureBuilder inside buildControlList(). So it is getting there. However, the FutureBuilder doesn't appear to be catching it and interpreting it as an errored "snapshot". Perhaps I'm not using the FutureBuilder correctly? There is also this from an "Unknown Source". Not sure how to interpret that.
Call stack:
Server._checkServerError (/mobile-client/lib/services/server.dart:75)
Server.getResponse (/mobile-client/lib/services/server.dart:112)
<asynchronous gap> (Unknown Source:0)
_ControlsScreen.buildControlList (/mobile-client/lib/screens/devices/controls.dart:71)
_ControlsScreen.build.<anonymous closure> (/mobile-client/lib/screens/devices/controls.dart:50)
Consumer.buildWithChild (/provider-4.0.5+1/lib/src/consumer.dart:175)
SingleChildStatelessWidget.build (/nested-0.0.4/lib/nested.dart:260)
StatelessElement.build (/lib/src/widgets/framework.dart:4576)
SingleChildStatelessElement.build (/nested-0.0.4/lib/nested.dart:280)
ComponentElement.performRebuild (/lib/src/widgets/framework.dart:4502)
Element.rebuild (/lib/src/widgets/framework.dart:4218)
StatelessElement.update (/lib/src/widgets/framework.dart:4583)
Element.updateChild (/lib/src/widgets/framework.dart:3201)
ComponentElement.performRebuild (/lib/src/widgets/framework.dart:4527)
Element.rebuild (/lib/src/widgets/framework.dart:4218)
StatelessElement.update (/lib/src/widgets/framework.dart:4583)
Element.updateChild (/lib/src/widgets/framework.dart:3201)
ComponentElement.performRebuild (/lib/src/widgets/framework.dart:4527)
Element.rebuild (/lib/src/widgets/framework.dart:4218)
ProxyElement.update (/lib/src/widgets/framework.dart:4862)
Element.updateChild (/lib/src/widgets/framework.dart:3201)
ComponentElement.performRebuild (/lib/src/widgets/framework.dart:4527)
Element.rebuild (/lib/src/widgets/framework.dart:4218)
ProxyElement.update (/lib/src/widgets/framework.dart:4862)
Element.updateChild (/lib/src/widgets/framework.dart:3201)
RenderObjectElement.updateChildren (/lib/src/widgets/framework.dart:5522)
MultiChildRenderObjectElement.update (/lib/src/widgets/framework.dart:5957)
Element.updateChild (/lib/src/widgets/framework.dart:3201)
ComponentElement.performRebuild (/lib/src/widgets/framework.dart:4527)
StatefulElement.performRebuild (/lib/src/widgets/framework.dart:4675)
Element.rebuild (/lib/src/widgets/framework.dart:4218)
StatefulElement.update (/lib/src/widgets/framework.dart:4707)
Element.updateChild (/lib/src/widgets/framework.dart:3201)
ComponentElement.performRebuild (/lib/src/widgets/framework.dart:4527)
Element.rebuild (/lib/src/widgets/framework.dart:4218)
ProxyElement.update (/lib/src/widgets/framework.dart:4862)
Element.updateChild (/lib/src/widgets/framework.dart:3201)
ComponentElement.performRebuild (/lib/src/widgets/framework.dart:4527)
StatefulElement.performRebuild (/lib/src/widgets/framework.dart:4675)
> _controlsFuture` **2)** Override method `initState` and do `_controlsFuture = widget.server.getResponse(widget.device, widget.controlSet)` inside **3)** In your builder do `future: _controlsFuture`
– Claudio Redi Jun 24 '20 at 14:27