0

Edit: Minimal reproducible example. Please have a look at the refresh-Method in main.dart.

I'm using a FutureBuilder to display the result of a network request. I call my fetch method in initstate (like suggested here):

Future<List<CheckIn>> futureCheckIn;

@override
void initState() {
  super.initState();
  _eventArg = Provider.of<EventArg>(context, listen: false);

  _helper = ApiHelper(context);
  futureCheckIn = _helper.fetchCheckIns(_eventArg.getEvent().id.toString());
}

I now want to use a RefreshIndicator to allow the user to refresh the screen. This works great until my network request ends with an exception (e.g. 404). Normally the exception get caught by the FutureBuilder but after excecution the request in the RefreshIndicator the exception is unhandled...

Here my code:

RefreshIndicator buildResult(List<CheckIn> checkIns, BuildContext context) {
return RefreshIndicator(
  key: _refreshKey,
  onRefresh: () async {
    setState(() {});
    futureCheckIn =
        _helper.fetchCheckIns(_eventArg.getEvent().id.toString()); // this could end with an exception
  },
  child: Container(
    height: MediaQuery.of(context).size.height,
    child: SingleChildScrollView(
      physics: AlwaysScrollableScrollPhysics(),
      scrollDirection: Axis.vertical,
      child: Column(
        children: [
          Container(
            margin: EdgeInsets.all(10),
            child: Text(
              constants.CHECK_IN_SCREEN_DESCRIPTION,
              style: TextStyle(fontSize: 16),
              textAlign: TextAlign.justify,
            ),
          ),
          ListView(
            physics: NeverScrollableScrollPhysics(),
            padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
            scrollDirection: Axis.vertical,
            shrinkWrap: true,
            children: checkIns.map((el) {
              return Card(
                elevation: 4,
                child: ListTile(
                  title: Text(el.name),
                  onTap: () async {
                   ...
                  },
                  trailing: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(
                        Icons.arrow_forward_ios,
                        color: Colors.black,
                      )
                    ],
                  ),
                ),
              );
            }).toList(),
          )
        ],
      ),
    ),
  ),
);

}

How can I use the RefreshIndicator properly with FutureBuilder?

Edit @Shri Hari: This exception is thrown

class AppException implements Exception {
 final _message;
 final _prefix;

 AppException([this._message, this._prefix]);

 String toString() {
   return "$_prefix$_message";
 }
}

class NotFoundException extends AppException {
  NotFoundException([String message]) : super(message, "Data not found: ");
}

enter image description here

Edit @Mhark Batoctoy: Yes it is part of the FutureBuilder:

@override
Widget build(BuildContext context) {
return FutureBuilder(
  future: futureCheckIn,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      List<CheckIn> checkIns = snapshot.data;

      return buildResult(checkIns, context); // RefreshIndicator is nested in here
    } else if (snapshot.hasError) {
      // Error handling
      ... 
    }

    // Spinner während der Datenabfrage
    return Center(
      child: CircularProgressIndicator(),
    );
  },
);

Unfortunatelly changing the onRefresh-Method from

 onRefresh: () async {
  setState(() {});
  futureCheckIn =
    _helper.fetchCheckIns(_eventArg.getEvent().id.toString()); // this could end with an exception
  },

to

onRefresh: () async {
  setState(() {});
  return futureCheckIn =
    _helper.fetchCheckIns(_eventArg.getEvent().id.toString()); // this could end with an exception
  },

or

onRefresh: () async {
  setState(() {});
  return _helper.fetchCheckIns(_eventArg.getEvent().id.toString()); // this could end with an exception
  },

does not change the error...

Matthias
  • 3,729
  • 22
  • 37

2 Answers2

2

Not sure if this is the best answer but I faced same problem and have to move ahead. So, this is what I learned and did.

RefreshIndicator don't like exception. That is even after moving them under try / catch or onError and consume exception successfully, RefreshIndicator still cries for unhandled exception (bit weird but I will have to investigate it more).

What I did was to make sure my API call don't throw any exception. That means, the API call itself (in this case fetchCheckIns() function call) catches all types of known / unknown exceptions [ catch (e) ] and set a flag in your response object that you are returning from the API (in this case the return type object of fetchCheckIns()).

Now, in your FutureBuilder, when you are calling buildResult(checkIns, context), check for snapshot.data and look for any failure flag. If you get failure, render the screen differently. That is:

    if (snapshot.hasData) {
      if (snapshot.data.error == true) {
         return ...; // Render error message
      } else {
         return buildResult(snapshot.data, context);
      }
    }

This also means, you may not be able to return List of objects and may have to wrap that in a single object which will contain list of objects.

Means, your CheckIn object may be wrapped with something like

class AllCheckIn {
   List<CheckIn> checkIns;
   bool error;
   String errorMsg;
}

Optionally, you can also add more description of error message based on your exception that you have received and captured in your API call and use that in your FutureBuilder.

Hope this is helpful.

Virendra
  • 41
  • 6
  • 1
    Yeah I thought that too and couldn't found any example that managed to handle the exceptions. Your approach sounds interesting. I'll use it the next time I'll need a RefreshIndicator. I'll give you feedback then. For now +1 and thanks for your reply @Virendra. – Matthias May 06 '21 at 18:40
0
 _helper.fetchCheckIns(_eventArg.getEvent().id.toString()); 

It returns a Future. Either a data that you need or an error. You can use the async await inside the try catch finally block or you can use Future.then() / Future.catchError / Future.whenComplete().

Example:

onRefresh: () async {
try {
   //You can use the await keyword here.
   return futureCheckIn =
    await _helper.fetchCheckIns(_eventArg.getEvent().id.toString());
}catch (err){
  //You can display the error here using a snackbar.
  }finally {
    //This executes whether there is an error or not. 
 }

},

  • Thanks for your response. I already tried using `await`, but as `futureCheckIn` is from type `Future>` it doesn't work because `await _helper.fetchCheckIns(_eventArg.getEvent().id.toString());` is from type `List`... – Matthias Sep 18 '20 at 12:07
  • Is your RefreshIndicator inside FutureBuilder? If so, try returning the _helper.fetchCheckIns() in your onRefresh() – Mhark Batoctoy Sep 18 '20 at 12:14
  • can you try putting your futureCheckIn inside the setState()? – Mhark Batoctoy Sep 18 '20 at 13:00
  • Unfortunately the exception still occures.. I will try to write a minimal reproducible example in the next day. – Matthias Sep 18 '20 at 13:16
  • Ok. Try putting the futureCheckIn = _helper.... inside the try catch block. – Mhark Batoctoy Sep 18 '20 at 13:22
  • I added a minimal reproducible example: https://github.com/klasenma/futurebuilder_with_refresh. – Matthias Sep 21 '20 at 08:47