【摘抄】Flutter Splash Screen — A simple way to handle async dependencies

341 阅读2分钟

Let’s be honest, who has a lot of time these days? Waiting for something to happen is not fun. So we always want everything instantly or as we developers put it: synchronous. But what do you do if you have someone that you need to wait for before you can start doing something? A so called asynchronous dependency. I can’t remember the last time I wrote an app that didn’t have some form of asynchronous dependency when it launched. This is what the splash screen is used for. Implementing one in Flutter is quite simple. Just change your MaterialApp to become a StatefulWidget, add bool _isInitialized = false; at the top and then initialize your dependencies in the initState() function, right?

bool _isInitialized = false;

@override
void initState() {
  super.initState();
  _initializeAsyncDependencies();
}

Future<void> _initializeAsyncDependencies() async {
  // do initialization
  setState(() {
    _isInitialized = true;
  });
}

So what’s wrong with that?

At first, nothing. But when your app and your requirements grow you can quickly run into maintainability issues with this approach. In the app that I am currently working on we started off like this but after a while we had so much stuff going on there that it was very hard to understand where, what, and how things are initialized. We have

  • translations coming from a server
  • a web-view that needs to initialized for cookie syncing purposes (a whole other topic, and trust me, not a fun one)
  • a configuration file that we need in order to render our main content remote configurations

and once everything was loaded we want to show a nice reveal transition to the main screen.

Again, all of this is possible and we did it. It just was a nightmare to work with.

So I sat down and thought about it… what if this could be done in a totally different way?

My goal was to convert the main app to a StatelessWidget and do all of our initialization somewhere decoupled. From a user's perspective, the behavior was not to be changed.

Turns out, the solution is simple and elegant.

···

First and only step

  runApp(
    SplashApp(
      key: UniqueKey(),
      onInitializationComplete: () => runMainApp(),
    ),
  );
}

void runMainApp() {
  runApp(
    MainApp(),
  );
}

Need elaboration?

Okay… create a SplashApp that does nothing other than initializing your asynchronous dependencies while showing a nice Splash Screen. Ask your favorite UI person for that.

Pass a callback function to the SplashApp that will be executed once everything is loaded.

The SplashApp

class SplashApp extends StatefulWidget {
  final VoidCallback onInitializationComplete;

  const SplashApp({
    Key key,
    @required this.onInitializationComplete,
  }) : super(key: key);

  @override
  _SplashAppState createState() => _SplashAppState();
}

class _SplashAppState extends State<SplashApp> {
  bool _hasError = false;

  @override
  void initState() {
    super.initState();
    _initializeAsyncDependencies();
  }

  Future<void> _initializeAsyncDependencies() async {
    // >>> initialize async dependencies <<<
    // >>> register favorite dependency manager <<<
    // >>> reap benefits <<<
    Future.delayed(
      Duration(milliseconds: 1500),
      () => widget.onInitializationComplete(),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Splash Screen',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: _buildBody(),
    );
  }

  Widget _buildBody() {
    if (_hasError) {
      return Center(
        child: RaisedButton(
          child: Text('retry'),
          onPressed: () => main(),
        ),
      );
    }
    return Center(
      child: CircularProgressIndicator(),
    );
  }
}

The callback just executes the run function for a different app, the MainApp n this case.

If you do this, then your SplashApp will be cleaned up and all you are left with is a MainApp in which you can access everything synchronously.

The MainApp

class MainApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // >>> use any dependency from your dependency manager <<<

    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

转载 =》gitconnected

You can find a full example of this on GitHub. Download it, use it, enjoy it.

github