FutureBuilder doesn't wait until future completes

2.6k views Asked by At

In my app I want to initialize something before my widgets will be created. I need to do it exactly in App class and trying to use FutureBuilder for this purpose. But _AppBlocProvider's build method is called before initInjectionContainer(), for example. My repository is not initialised yet in injectionContainer, but Blocs in provider are trying to access it's instance. What's wrong with this code?

enter image description here

I've also tried this:

void main() {
  runApp(App());
}

class App extends StatefulWidget {
  @override
  _AppState createState() => _AppState();
}

class _AppState extends State<App> {
  Future<bool>? _myFuture;

  Future<bool> _init() async {
    ...
    await initInjectionContainer();
    await sl<AudioManager>().preloadFiles();
    return false;
  }

  ...

  @override
  void initState() {
    super.initState();
    _myFuture = _init();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: _myFuture,
      builder: (context, _) {
        return _BlocProvider(
          child: Builder(
            builder: (context) => MaterialApp(
              title: 'Flutter Demo',
              theme: ThemeData(
                primarySwatch: Colors.blue,
              ),
              home: MainMenu(),
            ),
          ),
        );
      },
    );
  }
}

doesn't work.

1

There are 1 answers

4
Abion47 On BEST ANSWER

FutureBuilder doesn't just automatically block or show a loading screen or whatever. It builds once on initialization, and then again once the future completes. That second parameter in the builder that you anonymized is crucial to properly handling the state of the future and building accordingly.

FutureBuilder(
  future: _someFuture(),
  builder: (context, snapshot) {
    if (snapshot.connectionState != ConnectionState.done) {
      // Future not done, return a temporary loading widget
      return CircularProgressIndicator();
    }

    // Future is done, handle it properly
    return ...
  },
),

That being said, if there is stuff that your entire app needs that you need to initialize, you can call it from main before you call runApp so that they become a part of the runtime loading process rather than forcing a widget to deal with it:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await initInjectionContainer();
  await sl<AudioManager>().preloadFiles();

  runApp(App());
} 

Now with that being said, if these processes can take a while, it's better to handle them with a widget so that you can display a loading state to the user so they know the app didn't just freeze on start-up.