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