In this first section of the chapter, we’ll take a look at the Flutter CLI’s example code, review how to work with Dart code, and learn the basics of Flutter app architecture and implementation.
To create a new Flutter app, create a directory to contain the app’s source code and, inside it, another directory called lib. The lib directory is where we’ll add our Dart files defining what our app looks like and what our app does.
For now we won’t work in the CLI, but once we’re ready to build the app we will use flutter create to generate the files that are necessary for the app to build (which you’ll get to know and edit in the appendix about app configuration). In addition to the files and directories directly related to Flutter and your app, the root of your Flutter app must also contain an android and an ios directory containing the configuration for the build system for each platform.
Inside the lib directory, create a file called main.dart.
Outside lib, create a file called pubspec.yaml and paste this in it:
| name: flutter_example_name |
| description: Example description of a flutter app that makes |
| great things happen. |
| |
| dependencies: |
| flutter: |
| sdk: flutter |
| dev_dependencies: |
| flutter_test: |
| sdk: flutter |
| |
| flutter: |
| uses-material-design: true |
The pubspec.yaml file contains information about your project and its dependencies, which Flutter will need to build your app. Actually this file is required for any Dart package, but it’ll take a while and a few chapters before we start looking at Dart packages that aren’t Flutter apps.
Inside main.dart, write the following:
| import 'package:flutter/material.dart'; |
| |
| void main() => runApp(MyApp()); |
| |
| class MyApp extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| return MaterialApp( |
| title: 'Flutter Demo', |
| theme: ThemeData( |
| primarySwatch: Colors.blue, |
| ), |
| home: MyHomePage(title: 'Flutter Demo Home Page'), |
| ); |
| } |
| } |
| class MyHomePage extends StatefulWidget { |
| MyHomePage({Key key, this.title}) : super(key: key); |
| final String title; |
| |
| @override |
| _MyHomePageState createState() => _MyHomePageState(); |
| } |
| class _MyHomePageState extends State<MyHomePage> { |
| int _counter = 0; |
| |
| void _incrementCounter() { |
| setState(() { |
| _counter++; |
| }); |
| } |
| @override |
| Widget build(BuildContext context) { |
| return Scaffold( |
| appBar: AppBar( |
| title: Text(widget.title), |
| ), |
| body: Center( |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| Text( |
| 'You have pushed the button this many times:', |
| ), |
| Text( |
| '$_counter', |
| style: Theme.of(context).textTheme.display1, |
| ), |
| ], |
| ), |
| ), |
| floatingActionButton: FloatingActionButton( |
| onPressed: _incrementCounter, |
| tooltip: 'Increment', |
| child: Icon(Icons.add), |
| ), |
| ); |
| } |
| |
| } |
You’ll get a simple view with a counter and a floating action button which, when clicked, increments a counter that is shown at the center of the view, like in this screenshot:
If the app doesn’t work, try running:
| $ flutter create . |
As mentioned in the Preface, flutter create is the command used to create Flutter app projects, and executing it with a directory containing Flutter code will create the files and directories necessary for the app to compile for Android and iOS (and, as they become more stable, will also include the files needed for desktop development).
Keep in mind that you can fix most build problems not caused by errors in the code, especially after multiple builds, by running:
| $ flutter clean |
which will delete build files left by previous builds.
Let’s break down this code so that you can understand this example.
Before moving on to main.dart, which contains the main app code, we should take a brief look at the pubspec.yaml file, which we will discuss in greater detail in Chapter 4, Beyond the Standard Library: Plugins and Packages and, in even greater detail, in Appendix 2, Apple-Like Look and Additional App Configuration .
It is important to understand that the pubspec.yaml file is not unique to Flutter apps: it is a feature of Dart packages and, as such, also contains all the information needed to make it a Flutter app.
In the first part we’ll specify some metadata about the Flutter app. In this section, two attributes are set:
In order to avoid build issues and as general good practice, the name of the directory that contains the root of the project (the lib directory and the pubspec.yaml file) should be the same as the Dart package name.
| name: flutter_example_name |
| description: Example description of a flutter app that makes |
| great things happen. |
The rest of the file specifies the dependencies.
| dependencies: |
| flutter: |
| sdk: flutter |
| |
| dev_dependencies: |
| flutter_test: |
| sdk: flutter |
| |
| flutter: |
| uses-material-design: true |
Since we are not using any third-party packages, we are just specifying the Flutter SDK itself as a dependency and, with the lines:
| flutter: |
| uses-material-design: true |
we make sure that Material Design (Google/Android style) assets like icons are included.
| import 'package:flutter/material.dart'; |
This brings in the Flutter classes we need to use. In this chapter you’ll learn about the simplest ones and how to work with them (we’ll look at many others as we continue in Chapter 2, Laying Out More Widgets as well).
| void main() => runApp(new MyApp()); |
This line means that, when our code gets executed, we will start an app, and this app’s behavior and appearance is specified inside a class called MyApp, which is defined as:
| class MyApp extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| return MaterialApp( |
| title: 'Flutter Demo', |
| theme: ThemeData( |
| primarySwatch: Colors.blue, |
| ), |
| home: MyHomePage(title: 'Flutter Demo Home Page'), |
| ); |
| } |
| } |
Let’s break this down further.
| class MyApp extends StatelessWidget |
In Flutter every UI element or collection of UI elements is a widget, which is an immutable object. This means we can’t change a widget’s member variables: if there are any, they have to be declared as final or constant. You can find more information on Dart variables in Variables and Conditions.
There are two kinds of widgets:
Stateless widgets (subclasses of StatelessWidget), which get rendered when the app starts and when a parent widget gets re-rendered, like MyApp.
Stateful widgets (subclasses of StatefulWidget), which you can re-render at any time by calling the setState() function, like this app’s MyHomePage, which is covered in The Home Page Widget.
The app widget is the collection of every other widget, so it is a widget. In this case it is a StatelessWidget.
| @override |
| Widget build(BuildContext context) { |
| return MaterialApp( |
| title: 'Flutter Demo', |
| theme: ThemeData( |
| primarySwatch: Colors.blue, |
| ), |
| home: MyHomePage(title: 'Flutter Demo Home Page'), |
| ); |
| } |
This is the build method, which gets called when the widget needs to be rendered and returns a Widget object, which is every widget’s superclass.
In this case it gets called just once: when the app starts, given that MyApp doesn’t have a parent widget (it is every other widget’s parent widget).
In MyApp’s build method it is specified that the MyApp widget (the app itself) is a MaterialApp, which means that our app will use Material Design features that are part of the standard library.
The code sets, using the MaterialApp constructor, an app title, a theme with a primary swatch (a range of similar colors, as specified in Google’s Material Design guidelines), and a home page, which will be of class MyHomePage and have the title “Flutter Demo Home Page”.
This defines the aforementioned MyHomePage class, which is a StatefulWidget (a widget that has a state, unlike MyApp) with a title member and a state, which is _MyHomePageState:
| class MyHomePage extends StatefulWidget { |
| MyHomePage({Key key, this.title}) : super(key: key); |
| final String title; |
| |
| @override |
| _MyHomePageState createState() => _MyHomePageState(); |
| } |
In Dart, the underscore (_) in _MyHomePageState has an effect akin to the one private has in languages like Java and is explained in The underscore.
The Advantage of Stateful Widgets
Stateful widgets have the ability to be re-rendered programmatically, allowing the developer to build an app that reacts to changes in the data it is showing or representing.
Because of this, they are more complex than stateless widgets: a stateful widget has a state, which is represented by a State object, which contains the widget’s logic and mutable data. As I mentioned in the previous section, widgets themselves are immutable, so they need a different object (the State) to hold the mutable data.
They get re-rendered when you call the setState() function, which notifies the framework that the state of the widget has changed and it needs to be re-rendered (by calling its build method again) to show the changes to the user.
The MyHomePage Widget
The line:
| MyHomePage({Key key, this.title}) : super(key: key); |
defines MyHomePage’s arguments (as explained in Constructors). Since they are wrapped in braces, they are named arguments, which is why, when calling MyHomePage’s constructor, the syntax was:
| MyHomePage(title: 'Flutter Demo Home Page') |
The unused key parameter is used by the framework to identify which widgets to re-render. You can provide it and it can be useful, but it’s optional, and we won’t need it for this app.
We’ll talk about keys in the next chapter in The Key.
You can find more information on Dart class and constructor definitions in Classes.
Inside _MyHomePageState is where the app’s logic is defined:
| class _MyHomePageState extends State<MyHomePage> { |
| int _counter = 0; |
| |
| void _incrementCounter() { |
| setState(() { |
| _counter++; |
| }); |
| } |
| @override |
| Widget build(BuildContext context) { |
| return Scaffold( |
| appBar: AppBar( |
| title: Text(widget.title), |
| ), |
| body: Center( |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| Text( |
| 'You have pushed the button this many times:', |
| ), |
| Text( |
| '$_counter', |
| style: Theme.of(context).textTheme.display1, |
| ), |
| ], |
| ), |
| ), |
| floatingActionButton: FloatingActionButton( |
| onPressed: _incrementCounter, |
| tooltip: 'Increment', |
| child: Icon(Icons.add), |
| ), |
| ); |
| } |
| |
| } |
| class _MyHomePageState extends State<MyHomePage> |
_MyHomePageState is defined as the state of the widget MyHomePage.
| int _counter = 0; |
| |
| void _incrementCounter() { |
| setState(() { |
| _counter++; |
| }); |
| } |
This declares a counter and defines a function that increments the counter and triggers a re-render by changing the state of the widget (calling setState()).
The Main build Method for the Home Page
| @override |
| Widget build(BuildContext context) { |
| return Scaffold( |
| appBar: AppBar( |
| title: Text(widget.title), |
| ), |
| body: Center( |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| Text( |
| 'You have pushed the button this many times:', |
| ), |
| Text( |
| '$_counter', |
| style: Theme.of(context).textTheme.display1, |
| ), |
| ], |
| ), |
| ), |
| floatingActionButton: FloatingActionButton( |
| onPressed: _incrementCounter, |
| tooltip: 'Increment', |
| child: Icon(Icons.add), |
| ), |
| ); |
| } |
Use of a Widget to Provide the Basic App Structure: The Scaffold
This time the widget we return is a Scaffold, the basic Material Design visual layout structure; in other words, it is a generic container for all of our app content.
A Scaffold has an AppBar, which (using a Text widget) displays the title which was set earlier in MyHomePage’s constructor.
Build the App’s Body
The body of our Scaffold is wrapped inside a Center widget, which centers our content in the middle of the page.
The Center’s child (the content which it centers) is a Column, which displays its children in a vertical stack.
The first Text widget simply displays a String, whereas the second displays the counter with a built-in theme called display1.
Add the Floating Action Button
Not centered (but inside the Scaffold, outside the body, in its own floatingActionButton attribute) is the FloatingActionButton which, when pressed, calls the _incrementCounter function we saw previously.
Quite easy to understand are the tooltip code and the Icon widget (again, everything is a widget here) inside of it (its “child”) which is a simple plus (or “add”, as it is called) sign.
Every time we click the FAB the counter is incremented and the state changes, the setState() call triggers the build method again and the UI gets reloaded with the updated counter.
This is a very simple app: it’s just a MaterialApp that contains a Scaffold, with a simple FAB in the lower right and, in the middle, a Column with two Text widgets showing the number of times the FAB has been clicked.
This is very simple, but the only way to really understand how something is done is by doing it, so the next section is going to focus on adding new features to the existing code.