3

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)
downgrade
  • 88
  • 5
  • Are you totally certain that the exception is being thrown? Your code looks standard to me. – Claudio Redi Jun 24 '20 at 01:20
  • @ClaudioRedi yes, it definitely throws since the app halts and the debugger shows the exception originating within _checkServerError(). Will post stacktrace later today – downgrade Jun 24 '20 at 12:54
  • The only thing that makes some noise to me is how your using the future. Can you test this? **1)** Declare a class level variable `FutureBuilder> _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
  • @ClaudioRedi unfortunately, that didn't do it either :( – downgrade Jun 26 '20 at 01:24
  • @ClaudioRedi I have added the call stack in case that helps. I noticed there is an "asynchronous gap" in there but I'm not sure what to do with that. Have you seen that before? – downgrade Jun 26 '20 at 01:55

0 Answers0