Is it a good practice to execute futures together at Initialization of the app OR should it be executed one at a time?

148

At first glance, I'd suggest not blocking app startup with a call like this:

final initResults = await Future.wait(initFutures);

The user will not know anything is happening and will just see a blank screen for however long it takes for all the futures & initialization to complete.

Using a FutureBuilder is perhaps the most common way to "wait" for async data to arrive, but also to show the user something while waiting.

I just answered a question with similar requirements (waiting for multiple, dependent sequential async data). Perhaps that will give you a rough idea of how to structure your app start up.

Share:
148
Zenko
Author by

Zenko

Creating apps for a living and for fun!

Updated on December 29, 2022

Comments

  • Zenko
    Zenko 10 months

    Probably this is a noob question.

    Suppose I have a Flutter code that has a few things to initialize at beginning before calling runApp. Consider this code:

    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
    
      /// Handles localizations
      final flutterI18nDelegate = FlutterI18nDelegate(
          translationLoader: FileTranslationLoader(
              fallbackFile: 'en_US',
              basePath: 'assets/i18n',
              forcedLocale: Locale('en_US'),
              decodeStrategies: [JsonDecodeStrategy()]));
    
      /// Handles AppsFlyer Integrations
      final AppsFlyerOptions appsFlyerOptions = AppsFlyerOptions(
        afDevKey: 'MY-KEY',
        appId: 'APP-ID',
        showDebug: true,
      );
    
      final appsFlyerSdk = AppsflyerSdk(appsFlyerOptions);
    
      /// Starts the Repository
      final repository = Repository();
    
      /// Initialize Mobile Ads
      final mobileAds = MobileAds.instance.initialize().then((InitializationStatus status) =>
          print('Mobile Ads Initialized! Status: ${status.adapterStatuses}'));
    
      final List<Future<dynamic>> initFutures = [
        /// Load Localizations
        flutterI18nDelegate.load(null),
    
        /// Initialize Firebase
        Firebase.initializeApp(),
    
        /// Initialize Repository
        repository.initialize(),
    
        /// Initialize AppsFlyer SDK
        appsFlyerSdk.initSdk(),
    
        /// Initialize Ads
        mobileAds,
    
        /// Initialize adaptiveMode (Dark, Light, System)
        AdaptiveTheme.getThemeMode()
      ];
    
      /// Run everything together to save time
      final initResults = await Future.wait(initFutures);
      final FlutterI18n flutterI18n = initResults.first;
      final AdaptiveThemeMode themeSetting = initResults.last ?? AdaptiveThemeMode.system;
    
      
      runApp(
        RestartWidget(
          child: MultiRepositoryProvider(
              providers: [
                 /// ---- ALL Repository Needs ----
              ],
              child: MultiBlocProvider(
                  providers: [
                      /// ---- ALL Bloc ----
    
                  ],
                  child: MyApp(
                    flutterI18nDelegate: flutterI18nDelegate,
                    themeSetting: themeSetting,
                  ))),
        ),
      );
    }
    
    
    

    Notice that none of the futures being called depending on each other.

    But is it a good practice to run futures like this in the start of the app?

    Of course, the aim for executing futures together is to save time.

    OR Is it better to do it like this:

    /// Copying the futures part only 
    
      /// Load Localizations
      final FlutterI18n flutterI18n = await flutterI18nDelegate.load(null);
    
      /// Initialize Firebase
      await Firebase.initializeApp();
    
      /// Initialize Repository
      await repository.initialize();
    
      /// Initialize AppsFlyer SDK
      await appsFlyerSdk.initSdk();
    
      /// Initialize Ads
      await MobileAds.instance.initialize().then((InitializationStatus status) =>
          print('Mobile Ads Initialized! Status: ${status.adapterStatuses}'));
    
      /// Initialize adaptiveMode (Dark, Light, System)
      final AdaptiveThemeMode themeSetting = (await AdaptiveTheme.getThemeMode()) ?? AdaptiveThemeMode.system;
    

    Please feel free to point out anything I probably miss or can be a concern for doing this or it is all good? and why? (e.g: memory leak concerns?)

    Thanks

  • Zenko
    Zenko over 2 years
    Good suggestion! I'll do that. So do you think it is a good practice to put all together into one list of futures and execute them at once or should we execute them one after another? The question has been modified to accurately explain this point.
  • Baker
    Baker over 2 years
    @Zenko re: I think performance-wise they can be identical if you remove the await from each line on your 2nd code example. (You mention each future is independent of the others and order doesn't matter?) From a debugging and error-handling perspective, I would think the 2nd is easier to deal with. On a list of futures in .wait, any error will cause the future to return with that error. Any others are discarded: see api.flutter.dev/flutter/dart-async/Future/wait.html
  • Zenko
    Zenko over 2 years
    (You mention each future is independent of the others and order doesn't matter?) <== Yes
  • Zenko
    Zenko over 2 years
    The error handling perspective is a good one, thanks. However, if you remove the await from each line, then will the try catch block (in main.dart) going to catch the error? Although we do have the try catch block inside each of those method calls respectively, but the error message may not be shown to the user. Alternatively, we have to pass the context of the build and it will be messy.
  • Baker
    Baker over 2 years
    async / await (or lack thereof) shouldn't affect the scope of try/catch blocks dart.dev/codelabs/async-await#handling-errors. What changes by removing await is the order of completion, which is no longer guaranteed to be as written. So any of the async calls could throw and you'll have to decide whether to catch it locally or rethrow for handling higher up, say in main.dart. You still get to decide where & when to notify the user of the exception.