Why is futureBuilder stuck on the progress loader sometimes?
Execution is not advancing beyond this line:
var temp = await compute(parseEvents, response.data);
Probably parseEvents
is throwing something.
Notice how you never use try/catch
. You have to write code to catch exceptions. parseEvents
is probably throwing an exception (malformed json?, empty json->null?) and since you are not catching it you have no idea what is happening.
I would first try to get rid of the compute
and just call parseEvents
to see if the exception is silently ignored because of compute
. Then I would place try/catch
inside the parseEvents function and gradually have all execution paths to have try/catch
in them, including async
functions and await
calls.
Abeer Sul
Updated on December 18, 2022Comments
-
Abeer Sul over 1 year
I have an issue with futureBuilders in my whole flutter app. In each screen there can be 3 or 4 sections, each one loads data from a specific API endpoint. The problem is that sometimes, whole screen loads fine, and sometimes a section or two are stuck with the progress bar!.. I debugged and searched a lot and can't find the real cause. This is making me hate flutter :/
For example, here is the Home Page Code:
import 'package:carousel_pro/carousel_pro.dart'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:soul_space/PODOs/categories.dart'; import 'package:soul_space/PODOs/event.dart'; import 'package:soul_space/PODOs/image.dart'; import 'package:soul_space/PODOs/space.dart'; import 'package:soul_space/custom_widgets/category-card.dart'; import 'package:soul_space/custom_widgets/error-card.dart'; import 'package:soul_space/custom_widgets/no-data.dart'; import 'package:soul_space/custom_widgets/search-widget.dart'; import 'package:soul_space/services/categories-service.dart'; import 'package:soul_space/services/events-service.dart'; import 'package:flutter/foundation.dart'; import 'package:soul_space/services/spaces-service.dart'; import '../custom_widgets/home-row.dart'; import 'package:async/async.dart'; class HomeTab extends StatefulWidget { HomeTab({Key key, this.title, this.mainScaffoldKey}) : super(key: key); final GlobalKey<ScaffoldState> mainScaffoldKey; final String title; @override HomeTabState createState() => HomeTabState(); } class HomeTabState extends State<HomeTab> with AutomaticKeepAliveClientMixin { Future _homeImagesFuture; final AsyncMemoizer _categoriesMemoizer = AsyncMemoizer(); Future _categoriesFuture; final AsyncMemoizer _eventsMemoizer = AsyncMemoizer(); Future _eventsFuture; final AsyncMemoizer _spacesMemoizer = AsyncMemoizer(); Future _spacesFuture; var isLoading = false; final List<String> rowsTitles = ['New Events', 'Featured Spaces']; List colors = [ Colors.red[500], Colors.teal[300], Colors.yellow[500], Colors.orange[300], Colors.red[400], Colors.blue ]; Random random = new Random(); @override void initState() { super.initState(); _categoriesFuture = fetchCategories(); _eventsFuture = fetchEvents(); _spacesFuture = fetchSpaces(); _homeImagesFuture = getHomeImages(); } fetchCategories() async { return this._categoriesMemoizer.runOnce(() async { return await getCategories(); }); } fetchEvents() async { return this._eventsMemoizer.runOnce(() async { return await fetchAllEvents('approved'); }); } fetchSpaces() async { return this._spacesMemoizer.runOnce(() async { return await fetchAllSpaces('approved'); }); } String _homeSliderImages; Future<List<String>> getHomeImages() async { List<LoadedAsset> assets = await fetchSliderImages(); List<String> temp = []; for (var i = 0; i < assets.length; i++) { temp.add(assets[i].link); } return temp; } @override Widget build(BuildContext context) { super.build(context); debugPrint('REBUILD:' + DateTime.now().toString()); return Scaffold( body: SingleChildScrollView( child: Column( children: <Widget>[ Stack( children: [ SearchBar(scaffoldKey: widget.mainScaffoldKey), ], ), Padding( padding: const EdgeInsets.only(left: 10.0, right: 10.0), child: FutureBuilder( future: _eventsFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done) { debugPrint('***************************DONE1'); if (snapshot.hasError) { debugPrint(snapshot.error.toString()); return ErrorCard( isSmall: true, ); } else if (snapshot.hasData) { final List<Event> events = snapshot.data; return HomeRow( title: rowsTitles[0], events: events, route: '/allEvents'); } else { return NoDataCard( isSmall: true, ); } } else { debugPrint('***************************LOADER1'); return Container( margin: EdgeInsets.symmetric(vertical: 0.0), height: 250.0, child: Center(child: RefreshProgressIndicator())); } }), ), Padding( padding: const EdgeInsets.only(left: 10.0, right: 10.0), child: FutureBuilder( future: _spacesFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done) { debugPrint('***************************DONE2'); if (snapshot.hasError) { debugPrint(snapshot.error.toString()); return ErrorCard( isSmall: true, ); } else if (snapshot.hasData) { final List<Space> spaces = snapshot.data; return HomeRow( title: rowsTitles[1], spaces: spaces, route: '/allSpaces'); } else { return NoDataCard( isSmall: true, ); } } else { debugPrint('***************************LOADER2'); return Container( margin: EdgeInsets.symmetric(vertical: 0.0), height: 250.0, child: Center(child: RefreshProgressIndicator())); } }), ) ], ), )); } @override bool get wantKeepAlive => true; }
And this is a sample of a service function that parses json data from API:
Future<List<Event>> fetchAllEvents(String approval, {int id = -1}) async { var url = globals.apiUrl + '/events/' + (id != -1 ? id.toString() + '/' : '') + _getQueryParams(false, approval); dio.Response<String> response = await get(url); debugPrint('***************************GET SUCCEES1'); var temp = await compute(parseEvents, response.data); debugPrint('***************************PARSE SUCCEES1'); return temp; } . . List<Event> parseEvents(String responseBody) { final parsedJson = json.decode(responseBody); var list = parsedJson['data'] as List; if (list != null) { List<Event> eventsList = list.map((i) => Event.fromJson(i)).toList(); return eventsList; } else { return []; } }
Furthermore, here are examples of good and bad build logs, hope they help conclude the cause:
- 'LOADER' is printed when progress bar is displayed
- 'GET SUCCESS' is printed when get returns
- 'PARSE SUCCESS' is printed when compute function succeeds in parsing json
- 'DONE' is printed when the widget is loaded correctly
Any solutions?
EDIT
I removed "compute" function as suggested in the accepted answer and it fixed it, but not sure why it failed sometimes without exceptions
-
Kahou about 4 yearsThere is no
PARSE SUCCESS1
in bad logs, so future state still loading. -
Abeer Sul about 4 years@Kahou, yes but what is the explanation?
-
Abeer Sul about 4 yearsgood suggestion. I tried adding try/catch and keeping compute => still same issue without any execptions. Then I tried removing compute, tested it many times and seems like the issue is gone, any clue why? I will test more before I mark this as correct. But I would like to understand why compute hangs without exceptions. And will this affect app's performance?
-
Gazihan Alankus about 4 yearsDid you place try/catch inside
parseEvents
, around all lines of it? I never usedcompute
, should read up about how to properly handle errors while using it.