Loading screen in Flutter

Have you ever developed a mobile application that required to perform several tasks in the background before it loads? One way to go around this need is to put a loading screen. There are a few articles out there that describe how to do it for Android and iOS and how to perform the action before the flutter framework loads. However, the flutter framework doesn’t take long to load so one can still work with flutter to display the loading screen.

This article describes a basic loading screen that was developed for one of our projects. We started with the work of Karim Mohamed, https://pub.dartlang.org/packages/splashscreen, and modified it to execute tasks in the background with the ability to update the screen messages from the background tasks.

Step 1: Creating a StatefulWidget

The loading screen will be developed as a Material Widget in Flutter. As with any page in Flutter, we start with determining whether to create a Stateful or Stateless widget. The core requirement for the loading screen is to show messages while the background tasks are executed. As to show the message updating in real-time, we require the screen to refresh when there is a new message. Flutter Stateful Widgets provide the mechanism to refresh the widget paint handler whenever the state is updated. As this is a desirable feature, we will develop the loading screen by extending the StatefulWidget.

  1. class LoadingScreen extends StatefulWidget {
  2.   @override
  3.   LoadingScreenState createState() => LoadingScreenState();
  4. }
  5.  
  6. class LoadingScreenState extends State<LoadingScreen> {
  7.   @override
  8.   Widget build(BuildContext context) {
  9.   }	
  10. }

Step 2: Extending State class to contain the loading message

Following the single responsibility concept and SOLID principle direct references to the Loading Screen State need to be avoided. A new class extending the State class is created, called MessageState.

  1. import 'package:flutter/scheduler.dart';
  2. import 'package:flutter/widgets.dart';
  3.  
  4. class MessageState<T extends StatefulWidget> extends State<T> {
  5.   String _message;
  6.  
  7.   /// Setter for the message variable
  8.   set setMessage(String message) => setState(() {
  9.     _message = message;
  10.   });
  11.  
  12.   /// Getter for the message variable
  13.   String get getMessage => _message;
  14. }

Looking the purpose of this class from the lens of the Memento concept and we consider the MessageState class as the Memento, the Caretakers which are the classes called by the Originator, don’t need to know how the state is managed but only that it exists.

Now that we have a class representation for our widget state, we update the LoadingScreenState inheritance to use the MessageState class.

  1. class LoadingScreenState extends MessageState<LoadingScreen>

Step 3: Prepare the Widget display structure

For the purpose of this article, the loading screen will consist of a background image, a title for the application, a spinner loader and an area for the messages beneath the loader.

Loading Screeb Mockup

Loading Screeb Mockup

 

  1. Widget build(BuildContext context) {
  2.   return Scaffold(
  3.     backgroundColor: Colors.black,
  4.     body: new InkWell(
  5.       child: new Stack(
  6.         fit: StackFit.expand,
  7.         children: <Widget>[
  8.           /// Paint the area where the inner widgets are loaded with the
  9.           /// background to keep consistency with the screen background
  10.           new Container(
  11.             decoration: BoxDecoration(color: Colors.black),
  12.           ),
  13.           /// Render the background image
  14.           new Container(
  15.             child: Image.asset(‘assets/somebackground.png, fit: BoxFit.cover),
  16.           ),
  17.           /// Render the Title widget, loader and messages below each other
  18.           new Column(
  19.             mainAxisAlignment: MainAxisAlignment.start,
  20.             children: <Widget>[
  21.               new Expanded(
  22.                 flex: 3,
  23.                 child: new Container(
  24.                     child: new Column(
  25.                       mainAxisAlignment: MainAxisAlignment.start,
  26.                       children: <Widget>[
  27.                         new Padding(
  28.                           padding: const EdgeInsets.only(top: 30.0),
  29.                         ),
  30.                         ‘Application Title’,
  31.                       ],
  32.                     )),
  33.               ),
  34.               Expanded(
  35.                 flex: 1,
  36.                 child: Column(
  37.                   mainAxisAlignment: MainAxisAlignment.center,
  38.                   children: <Widget>[
  39.                     /// Loader Animation Widget
  40.                     CircularProgressIndicator(
  41.                       valueColor: new AlwaysStoppedAnimation<Color>(
  42.                           Colors.green),
  43.                     ),
  44.                     Padding(
  45.                       padding: const EdgeInsets.only(top: 20.0),
  46.                     ),
  47.                     Text(getMessage),
  48.                     ],
  49.                   ),
  50.                 ),
  51.               ],
  52.             ),
  53.           ],
  54.         ),
  55.       ),
  56.     );
  57.   }
  58. }

Step 4: Handle the background tasks

Using the State class initState method, we kickoff an asynchronous process that executes the tasks sequentially.

  1. /// Initialise the state
  2. @override
  3. void initState() {
  4.   super.initState();
  5.  
  6.   /// If the LoadingScreen widget has an initial message set, then the default
  7.   /// message in the MessageState class needs to be updated
  8.   if (widget.initialMessage != null) {
  9.     initialMessage = widget.initialMessage;
  10.   }
  11.  
  12.   /// We require the initializers to run after the loading screen is rendered
  13.   SchedulerBinding.instance.addPostFrameCallback((_) {
  14.     runInitTasks();
  15.   });
  16. }
  17.  
  18. /// This method calls the initializers and once they complete redirects to
  19. /// the widget provided in navigateAfterInit
  20. @protected
  21. Future runInitTasks() async {
  22. /// Run each initializer method sequentially
  23.   Future.forEach(widget.initializers, (init) => init(this)).whenComplete(() {
  24.     Navigator.of(context).pushReplacement(new MaterialPageRoute(
  25.       builder: (BuildContext context) => NextScreen()));
  26.   });
  27. }

The most important line in the code above is the SchedulerBinding.instance.addPostFrameCallback line. The addPostFrameCallback ensures that the asynchronous process starts only after the first rendering of the screen has been completed. This ensures that the user sees something on the screen rather than rendering at the end.

Finally the section Navigator.of(context).pushReplacement tells the application to replace the loading screen widget with the widget of choice, in this case the NextScreen widget.

The code above is simplified to explain the structure and code. A proper Flutter Plugin structure and code solution can be found in our github: https://github.com/kdemanuele/Flutter-Loading-Screen.

Screenshot of the Loader Screen in the plugin example

Screenshot of the Loader Screen in the plugin example

References