Why is futureBuilder stuck on the progress loader sometimes?

Abeer Sul :

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 :/

enter image description here

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

enter image description here

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

Gazihan Alankus :

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.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=175114&siteId=1