Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is futureBuilder stuck on the progress loader sometimes?

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

like image 879
Abeer Sul Avatar asked May 12 '26 09:05

Abeer Sul


1 Answers

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.

like image 79
Gazihan Alankus Avatar answered May 15 '26 00:05

Gazihan Alankus



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!