Async initialization of services during startup

Async initialization of services during startup

Startup phases:

  1. Bootstrapping the native platform code
  2. Bootstrapping the Flutter code
  3. Normal application state where user interaction is allowed

Bootstrapping the native platform code

The first thing that happens when a Flutter app starts, is the bootstrapping of the native platform code. This piece is responsible for showing the splash screen and creating a container where the Flutter app can live. As soon as everything is ready, the main function in the /lib folder is executed. This main function is responsible for bootstrapping the Flutter code.

Bootstrapping the Flutter code

The bootstrapping of the Flutter code is where things might get complicated due to the async nature of Flutter and the usage of Provider to provide services and state. The reason for this is:

  1. When using Provider for services and state, these services and state are part of the widget tree.
  2. Some services are initialized asynchronously. For example Sqflite and all other services that are using method channels to access the underlying platform.
  3. The widget tree must build to have a user interface
  4. The building of the user interface depends on services and state defined in step 1

To complicate things even further, some services are dependant on other services and the order of initialization of the services might matter.

One might try to initialize all the services before runApp(MyApp()) is called.

Future<void> main() async {
   await initAllServicesAndState();
   runApp(MyApp());
}

This has some major downsides:

  • There is no widget tree yet, getting the services into the widget tree from this point is hacky.
  • If something fails during initialization and you want the user to decide how to recover, there is no easy way to show some UI.

Another approach is to initially build a user interface that is not dependant on any services or state. The only responsibility of this page is to initialize things. In my project, this is called AppStartPage.

AppStartPage

I decided to take the following approach:

  1. Let Provider create the services and states
  2. Set AppStartPage as ‘home’ in the MaterialApp
  3. Let AppStartPage initialize the services
  4. When initialization is done, replace the AppStartPage with the HomePage of your app
class AppStartPage extends StatefulWidget {
  @override
  \_AppStartPageState createState() => \_AppStartPageState();
}

class \_AppStartPageState extends State<AppStartPage> {
  @override
  void initState() {
    super.initState();
    onStart();
  }

  Future<void> onStart() async {
    await Provider.of<VersionService>(context, listen: false).init();
    await Provider.of<SoundService>(context, listen: false).init();
    await Provider.of<ConfigurationBoxService>(context, listen: false).init();
    await Provider.of<ConfigurationLoader>(context, listen: false).init();
    await Provider.of<LoggingServiceFile>(context, listen: false).init();
    Provider.of<LoggingServiceFile>(context, listen: false).connectLogWriter();
    await Provider.of<DatawedgeService>(context, listen: false).init();
    await Navigator.pushReplacement(context, HomePage.Route);
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

In this sample, the AppStartPage is an empty container. In a real app, you might want to show a page that informs the user what is going on, like ‘Loading….’.

A downside is that the app now has two splash screens. The first is coming from the Android/iOS project. The second one is AppStartPage.

But the advantages of an AppStartPage are obvious:

  1. Everything is initialized properly and in the right order when the app starts
  2. No user interaction is possible before the initialization is done, so no-undefined state.
  3. When there is a problem during initialization, you can ask the user in a pop-up what to do.
  4. It is super simple….