A Brief Introduction to Flutter
Flutter
A few success stories
How Flutter works
Dart
• Open-source web programming language
developed by Google
• Class-based, single-inheritance, object-oriented
language with C-style syntax
• Supports interfaces, abstract classes, reified
generics, optional, and strong typing
• Dart compiles to ARM and x86 code
– Dart mobile apps can run natively on iOS, Android
– For web apps, Dart translates to JavaScript
Dart
• Everything you can place in a variable is an object
– Even numbers, functions, and null are objects
• Type annotations are optional
– If no type is expected, use the special type dynamic
• Dart supports generic types, like List<int>
• Dart supports
– Top-level functions (such as main()), as well as functions
tied to a class or object
– Top-level variables, as well as variables tied to a class or
object
• Public, protected, and private properties do not exist
– If an identifier starts with an underscore (_), it is private to
its library
Credits and installation
• Heavily based on https://flutter.dev/docs
• First you must
– Install flutter (v 1.20.4)
– Install IDE, Simulator, and editor (e.g., VS code)
• xCode, Android Studio, IntelliJ
– Test everything
First app
• flutter create my_app
• cd my_app
• open -a Simulator
– on my Mac
• flutter run
First app
// Copyright 2018 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
child: Text('Hello World'),
),
),
);
}
}
Minimal app
• Function runApp() takes the given Widget and
makes it the root of the widget tree
• The widget tree consists of two widgets,
the Center widget and its child, the Text widget
• The framework forces the root widget to cover
the screen, which means the text “Hello, world”
ends up centered on screen
Minimal app (I)
import 'package:flutter/material.dart';
void main() {
runApp(
Center(
child: Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
Text direction problem
• Flutter doesn't know whether the text is LTR or
RTL
– You can tell it the textDirection explicitly
– You can just wrap the Text with a Directionality Widget
– Frame everything with a MaterialApp widget
• The default localization in the widgets and material
libraries is LTR
Text('Hello', textDirection: TextDirection.ltr,),
Directionality(textDirection: TextDirection.ltr,
child: new Text('Hello'),),
Minimal app (II)
import 'package:flutter/material.dart’;
void main() {
runApp(
MaterialApp(
title: 'Welcome to Flutter’,
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter’),
),
body: Center(
child: Text(
'Hello World !’,
style: TextStyle(
fontSize: 30,
fontFamily: 'Futura’,
color: Colors.blue),
),
),
),
),
);
}
Observations
• This example creates a Material app
– Flutter offers a rich set of Material widgets
– iOS-centric applications can use package Cupertino
• The app extends StatelessWidget which makes the app
itself a widget
– Almost everything is a widget, including alignment,
padding, and layout
• The Scaffold widget, from the Material library,
provides a default app bar, title, and a body property
that holds the widget tree for the home screen
• The body for this example consists of a Center widget
containing a Text child widget
GUI
• Flutter’s UI is built in code
• Widgets are the basic building blocks of a Flutter
UI
– Almost everything in Flutter is a widget
• A widget is an immutable object that describes a
specific part of a UI
• Widgets are composable, meaning, that you can
combine existing widgets to make more
sophisticated widgets
Widgets
• You build your UI out of widgets
– Flutter widgets are built using a modern framework that
takes inspiration from React
• Widgets describe what their view should look like
given their current configuration and state
– When a widget’s state changes, the widget rebuilds its
description
– The framework computes the diff against the previous
description to determine the minimal changes needed in
the underlying render tree to transition from one state
to the next
New widgets
• Are subclasses of either StatelessWidget
or StatefulWidget
• A widget’s main job is to implement
function build() to describe the widget in terms of
other, lower-level widgets
– The framework builds those widgets until the process
reaches widgets that represent the
underlying RenderObject, which computes and
describes the geometry of the widget
Everything is a widget
Stateful vs. stateless widgets
• If a widget can change when the user interacts
with it, it is stateful
– A widget’s state consists of values that can change, like
a slider’s current value or whether a checkbox is
checked
• A widget’s state is stored in a State object, which
separates the widget’s state from its appearance
• When the widget’s state changes, the state object
calls setState(), telling the framework to redraw
the widget
• A stateless widget has no internal state to manage
How widgets work
• Widgets are passed as arguments to other widgets
• The Scaffold widget takes a number of different
widgets as named arguments
– Each is placed in the Scaffold layout in the appropriate
place
• The AppBar widget lets you pass in widgets for
the leading widget, and the actions of
the title widget
• This pattern recurs throughout the framework
Basic widgets
Rows and Columns
• Row and Column are classes that contain and lay
out widgets
– Widgets inside of a Row or Column are called children
– Row and Column are referred to as parents
Another example
Left Column
Structure
Our first widget
class BlueBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.blue,
border: Border.all(),
),
);
}
}
Example
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter’,
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter’),
),
body: Center(
child: Column(
children: [
BlueBox(),
BlueBox(),
BlueBox()
],
),
),
),
);
}
}
Additional attributes
• MainAxisSize (min or max) determines how much
space a Row and Column can occupy on their
main axes
• MainAxisAlignment (start, end, center,
spaceBetween, spaceEvenly, spaceAround)
positions children with respect to main axis
• crossAxisAlignment (many options) positions
children with respect to cross axis
Cross axis
Example
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [BlueBox(), BiggerBlueBox(), BlueBox()],
),
),
Flexible
• Flexible wraps a widget, so the widget becomes
resizable
– After inflexible widgets are laid out, the widgets are
resized according to their flex and fit properties
– Flex compares itself against other flex properties before
determining what fraction of the total remaining space
each Flexible widget receives
– Fit determines whether a Flexible widget fills all of its
extra space
• FlexFit.loose uses the widget’s preferred size (Default)
• FlexFit.tight forces the widget to fill all of its extra space
Flex example
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
BlueBox(),
Flexible(
fit: FlexFit.tight,
flex: 1,
child: BlueBox(),
),
Flexible(
fit: FlexFit.tight,
flex: 3,
child: BlueBox(),
),
],
),
),
Other options
• Similar to Flexible, Expanded can wrap a widget
and force the widget to fill extra space
• SizedBox can wrap a widget and resizes it using
properties height and width
– It can also use height and width to create empty space
• Spacer can create space between widgets
– Use Spacer to create space using a flex property
– Use SizedBox to create space using a specific number of
logical pixels
SizedBox
children: [
BlueBox(),
SizedBox(width: 50),
SizedBox(
width: 100,
child: BlueBox(),
),
BlueBox(),
]
Spacer widget
child: Row(
children: [
BlueBox(),
Spacer(flex: 1),
BlueBox(),
Spacer(flex: 1),
BlueBox(),
],
)
Icon widget
• Flutter is preloaded with icon packages
for Material and Cupertino applications
body: Center(
child: Row(
children: [
Icon(
Icons.add_circle,
size: 50,
color: Colors.orange,
),
Icon(
Icons.widgets,
size: 50,
color: Colors.red,
),
],
),
),
Image widget
• To use local images you need perimissions
body: Center(
child: Row(
children: [
Image.network('https://...’),
],
),
)
Wireframes
• press "p" in the console
Assets (pubspec.yaml)
• To include all assets under a directory, specify the
directory name with the / character at the end
• Only files located directly in the directory are
included
• To add files located in subdirectories, create an
entry per directory
flutter:
uses-material-design: true
assets:
- images/lake.jpg
- assets/background.png
Exercise
body: Column(
children: [
Row(
One simple solution
children: [
Icon(),
Column(
children: [
Text(),
Text(),
],
),
],
),
SizedBox(),
Row(
children: [
Text(),
Text(),
],
),
SizedBox(),
Row(
children: [
Icon(), Icon(), Icon(),Icon()
],
),
],
)
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(Icons.account_circle, size: 50),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text('Flutter McFlutter',
style: Theme.of(context).textTheme.headline5),
Text('Experienced App Developer'),
],
),
],
),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('123 Main Street'),
Text('415-555-0198'),
],
),
SizedBox(height: 16),
Row(
children: [
Icon(Icons.accessibility, size: 30),
Icon(Icons.timer, size: 30),
Icon(Icons.phone_android, size: 30),
Icon(Icons.phone_iphone, size: 30),
],
mainAxisAlignment: MainAxisAlignment.spaceAround,
),
],
),
ListView widget
body: ListView(
padding: EdgeInsets.all(8),
children: [
Container(
height: 50,
color: Colors.amber[600],
child: Center(child: Text('Entry A')),
),
Container(
height: 50,
color: Colors.amber[500],
child: Center(child: Text('Entry B')),
),
Container(
height: 50,
color: Colors.amber[100],
child: Center(child: Text('Entry C')),
),
],
),
primarySwatch
• primarySwatch is not a Color, but a MaterialColor
– It is all the different shades of a color a material app can
use
• PrimaryColor is one of those shades
– It is normally equal to primarySwatch[500]
• It is usually better to define a primarySwatch
instead of a primaryColor
– Some material components may use a different shade of
the primaryColor for things such as shadow, border, ...
Modularization
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Retrieve Text Input',
home: MyCustomWidget(),
);
}
}
CupertinoApp
import 'package:flutter/cupertino.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoApp(
title: 'Flutter SafeArea Cupertino’,
home: CupertinoPageScaffold(
backgroundColor: CupertinoColors.extraLightBackgroundGray,
navigationBar: CupertinoNavigationBar(
backgroundColor: CupertinoColors.lightBackgroundGray,
middle: Text('SafeArea Test Page’),
),
child: Screen(),
)
);
}
}
@override
Result
class Screen extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: Row(
children: [BlueBox(),BlueBox(),BlueBox()],
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
)
);
}
}
class BlueBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: CupertinoColors.activeBlue,
border: Border.all(),
),
);
}
}
Buttons
(stateless)
body: FlatButton(
color: Colors.blue,
textColor: Colors.white,
disabledColor: Colors.grey,
disabledTextColor: Colors.black,
padding: EdgeInsets.all(8.0),
splashColor: Colors.blueAccent,
onPressed: () {
/*...*/
},
child: Text(
’Flat Button’,
style: TextStyle(fontSize: 20.0),
),
),
Stateful widgets
• Maintain state that might change during the
lifetime of the widget
– Can change their appearance in response to user
events or when they receive data
• Checkbox, Radio, Slider, InkWell, Form,
and TextField are examples
• We always have two classes that extend
StatefulWidget and State
– State consists of values that can change, like a slider’s
current value
– State contains the widget’s mutable state and the
widget’s build() method
– When the widget’s state changes, the state object
calls setState(), telling the framework to redraw the
widget
Initial app
Stateless widget
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,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page’),
);
}
}
StatefulWidget
• This class is the configuration for the state
• It holds the values (title) provided by the parent (
MyApp) and used by the build method of State
• Fields in a widget subclass are always marked final
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
State
• The call to setState tells Flutter that something
has changed in this State, which causes it to rerun
the build method
– The display can then reflect the updated values
– If we changed _counter without calling setState(), build
would not be called and nothing would happen
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: [
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Theme.of(context).textTheme.headline4
• Method Theme.of(context) looks up the widget
tree and returns the nearest Theme in the tree
– If you have a standalone Theme defined above your
widget, that’s returned
– If not, the app’s theme is returned
build()
• The method is rerun every time setState() is
called, for instance as done by method
_incrementCounter
• Flutter has been optimized to make rerunning
build methods fast
– It rebuilds anything that needs updating rather than
changing widgets individually
Another example
class FavoriteWidget extends StatefulWidget {
@override
_FavoriteWidgetState createState() => _FavoriteWidgetState();
}
class _FavoriteWidgetState extends State<FavoriteWidget> {
bool _isFavorited = true;
int _favoriteCount = 41;
void _toggleFavorite() {
setState(() {
if (_isFavorited) {
_favoriteCount -= 1;
_isFavorited = false;
} else {
_favoriteCount += 1;
_isFavorited = true;
}
});
}
build()
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_isFavorited ? Icon(Icons.star) : Icon(Icons.star_border)),
color: Colors.red[500],
onPressed: _toggleFavorite,
),
),
SizedBox(
width: 18,
child: Container(
child: Text('$_favoriteCount'),
),
),
],
);
}
}
Who manages the state of a stateful widget
• If the state is user data, for example the checked
or unchecked mode of a checkbox, the state is
best managed by the parent widget
• If the state is aesthetic, for example an animation,
then the state is best managed by the widget itself
• If in doubt, start by managing the state in the
parent widget
Widget manages its own state
• Class _TapboxAState
– Manages state for TapboxA
– Defines boolean _active that determines the box’s
current color
– Defines method _handleTap() that updates _active
when the box is tapped and calls method setState() to
update the UI
– Implements all interactive behavior for the widget
Example
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
),
body: Center(
child: TapboxA(),
),
),
);
}
}
class TapboxA extends StatefulWidget {
@override
_TapboxAState createState() => _TapboxAState();
}
class _TapboxAState extends State<TapboxA> {
bool _active = false;
void _handleTap() {
setState(() {
_active = !_active;
});
}
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
child: Center(
child: Text(
_active ? 'Active' : 'Inactive',
style: TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: _active ? Colors.red : Colors.blue,
),
),
);
}
}
Parent widget manages the widget’s state
• Example
– IconButton is a stateless widget but the parent widget
needs to know whether the button has been tapped
• TapboxB exports its state to its parent through a
callback
– Extends StatelessWidget because its state is handled by its
parent
– When a tap is detected, it notifies the parent
• Class ParentWidgetState
– Manages state _active for TapboxB
– Implements method _handleTapboxChanged() called
when the box is tapped
– When the state changes, calls setState() to update the UI
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
void main() => runApp(MyApp());
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return Container(
child: TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
class TapboxB extends StatelessWidget {
TapboxB({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
child: Center(
child: Text(
active ? 'Active' : 'Inactive',
style: TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
Constructors
class Point {
double x, y;
Point(double x, double y) {
this.x = x;
this.y = y;
}
Point(this.x, this.y);
}
Functions
• When defining a function, use {param1, param2,
…} to specify named parameters
• Although named parameters are a kind of
optional parameter, you can annotate them with
@required to indicate that the parameter is
mandatory
– @required implies the foundation library
• A function can use = (or :) to define default values
for both named and positional parameters
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
),
body: Center(
child: ParentWidget(),
),
),
);
}
}
External packages
• We cannot use any package freely, but those
already part of the distribution
• We must edit pubspec.yaml to added
dependencies
– For example to pub.dev packages
pubspec.yaml
• Change pubspec.yaml
• Execute flutter pub get
– This pulls the package(s) into your project
– It also auto-generates the pubspec.lock file with a list of
all packages pulled into the project and their version
numbers
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.3
english_words: ^3.1.5
// Copyright 2018 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final wordPair = WordPair.random();
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
child: Text(wordPair.asPascalCase),
),
),
);
}
}
More examples and apps
Navigation
• Almost all applications comprise diverse screens
• In Flutter, screens and pages are called routes
– In Android, a route is equivalent to an Activity
– In iOS, a route is equivalent to a ViewController
– In Flutter, a route is just a widget
• Flutter provides Navigator to navigate to new
routes
– An app can use more than one Navigator
Two options
• Most mobile apps display screens on top of each
other, like a stack
– MaterialApp and CupertinoApp already use a Navigator
under the hood
• Navigator 1.0
– Routes are pushed and popped onto the Navigator’s
stack with either named routes or anonymous routes
• Navigator 2.0
– It was difficult to push or pop multiple pages, or remove
a page underneath the current one
– Makes the app’s screens a function of the app state
Navigator 1.0
• Navigator.push() to navigate to a second route
– Adds a Route to the stack of routes managed by the
Navigator
– The previous screen is still part of the widget tree, so
any State object associated with it stays around
while the other screen is visible
• Navigator.pop() to return to initial one
– Removes the current Route from the stack of routes
managed by the Navigator
Example (FirstRoute)
class FirstRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: RaisedButton(
child: Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondRoute()),
);
}
),
),
);
}
}
Example (SecondRoute)
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
Navigation with named routes
• To navigate to the same screen in many parts of
an app
• We can define a named route, and use the named
route for navigation
– Named routes require method Navigator.pushNamed()
Named routes
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Named Routes Demo',
initialRoute: '/',
routes: {
'/': (context) => FirstRoute(),
'/second': (context) => SecondRoute(),
},
);
}
}
First route
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Launch screen'),
onPressed: () {
Navigator.pushNamed(context, '/second');
},
),
),
);
}
}
Pass arguments to a named route
• In some cases, when navigating you might also
need to pass arguments to a named route
– For example, you might wish to navigate to the /user
route and pass information about a particular user
• Navigator.pushNamed() has a parameter for this
• Two ways
– ModalRoute.of
– onGenerateRoute()
Step 1
• Define the arguments you need to pass
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
Step 2
• Create a widget that extracts the arguments
class ExtractArgumentsScreen extends StatelessWidget {
static const routeName = '/extractArguments';
@override
Widget build(BuildContext context) {
final ScreenArguments args = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(
title: Text(args.title),
),
body: Center(
child: Text(args.message),
),
);
}
}
Step 3
• Register the widget in the routes table
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
ExtractArgumentsScreen.routeName: (context) => ExtractArgumentsScreen(),
},
title: 'Navigation with Arguments',
home: HomeScreen(),
);
}
}
Step 4
• Finally, navigate to the ExtractArgumentsScreen
when a user taps a button using
Navigator.pushNamed()
• Provide the arguments to the route via the
arguments property
• The ExtractArgumentsScreen extracts the title
and message from these arguments
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RaisedButton(
child: Text("Navigate to screen that extracts arguments"),
onPressed: () {
Navigator.pushNamed(
context,
ExtractArgumentsScreen.routeName,
arguments: ScreenArguments(
'Extract Arguments Screen',
'This message is extracted in the build method.',
),
);
},
),
],
),
),
);
}
}
Alternatively
• Extract the arguments using onGenerateRoute
• Instead of extracting the arguments directly inside
the widget, you can also extract the arguments
inside method onGenerateRoute() and pass them
to a widget
• Method onGenerateRoute() creates the correct
route based on the given RouteSettings
onGenerateRoute
return MaterialApp(
onGenerateRoute: (settings) {
// Handle '/’
if (settings.name == '/') {
return MaterialPageRoute(builder: (context) => HomeScreen());
}
// Handle '/details/:id’
var uri = Uri.parse(settings.name);
if (uri.pathSegments.length == 2 &&
uri.pathSegments.first == 'details') {
var id = uri.pathSegments[1];
return MaterialPageRoute(builder: (context) =>
DetailScreen(id: id));
}
return MaterialPageRoute(builder: (context) => UnknownScreen());
},
);
child: FlatButton(
child: Text('View Details’),
pushNamed
onPressed: () {
Navigator.pushNamed(context, '/details/1');
},
),
Return data from a screen
• In some cases, you might want to return data
from a new screen
– For example, you push a new screen that presents two
options to a user
– When the user taps an option, you want to inform the
first screen of the user’s selection so that it can act on
that information
• We can use Navigator.pop()
Final result
Define the home screen
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Returning Data Demo'),
),
body: Center(child: SelectionButton()),
);
}
}
Add a button that launches the selection screen
• Launches the SelectionScreen when it is tapped
• Waits for the SelectionScreen to return a result
class SelectionButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: Text('Pick an option, any option!'),
);
}
}
Asyncronous programming
• A future represents the result of an asynchronous
operation
– It can have two states: uncompleted or completed
• To define an async function, add async before the
function body
– We can use keyword await to wait for a future to
complete
– Keyword await only works in async functions
How to use returned data
_navigateAndDisplaySelection(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()));
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("$result")));
}
class SelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pick an option'),
Show the
),
body: Center(
child: Column(
selectionmainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
screen
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
with two onPressed: () {
// Pop here with "Yep"...
buttons
},
child: Text('Yep!'),
),
• Build a selection
),
screen that contains
Padding(
two buttons padding: const EdgeInsets.all(8.0),
• When a user tapschild:
a RaisedButton(
onPressed: () {
button, the app closes
// Pop here with "Nope"
the selection screen
},
and lets the homechild: Text('Nope.'),
screen know which
),
button was
… tapped
}
How to return data
• Navigator.pop() accepts an optional second
argument called result
RaisedButton(
onPressed: () {
Navigator.pop(context, 'Yep!');
},
child: Text('Yep!'),
),
RaisedButton(
onPressed: () {
Navigator.pop(context, 'Nope.');
},
child: Text('Nope.'),
),
Send data to a new screen
• Often, you not only want to navigate to a new
screen, but also pass data to the screen
– For example, you might want to pass information about
the item that’s been tapped
• For example, you can create a list of todos
– Define a todo class
– Display a list of todos
– Create a detail screen that can display information about
a todo
– Navigate and pass data to the detail screen
Class Todo
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class Todo {
final String title;
final String description;
Todo(this.title, this.description);
}
void main() {
runApp(MaterialApp(
title: 'Passing Data',
home: TodosScreen(
todos: List.generate(
20,
(i) => Todo(
'Todo $i',
'A description of what needs to be done for Todo $i',
),
),
),
));
}
class TodosScreen extends StatelessWidget {
final List<Todo> todos;
TodosScreen({Key key, @required this.todos}) : super(key: key);
TodosScreen
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Todos'),
),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(todos[index].title),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(todo: todos[index]),
),
);
},
);
},
),
);
}
}
DetailScreen
class DetailScreen extends StatelessWidget {
final Todo todo;
DetailScreen({Key key, @required this.todo}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(todo.title),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Text(todo.description),
),
);
}
}
Example
Navigator 2.0
• Router configures the list of pages to be displayed
by the Navigator
– Usually this list of pages changes based on the current
state of the app
• RouterDelegate defines app-specific behavior of
how the Router learns about changes in app state
and how it responds to them
– Its job is to listen to the RouteInformationParser and
the app state and build the Navigator with the current
list of Pages
• BackButtonDispatcher reports back button
presses to the Router
How does it work?
Tabs
• Create a TabController
• Create the tabs
• Create content for each tab
class TabBarDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
title: Text('Tabs Demo'),
),
body: TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
),
),
);
}
}
Fetch data from the Internet
• Fetching data from the internet is necessary for
most apps
• Package http is the solution
– This package is supported on Android, iOS, and the web
• To install the http package, add it to the
dependencies section of the pubspec.yaml
– Execute flutter pub get
dependencies:
http: ^0.12.2
flutter:
sdk: flutter
Example (I)
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(MyApp(post: fetchPost()));
Future<Post> fetchPost() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/posts/1');
if (response.statusCode == 200) {
return Post.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load post');
}
}
Example (II)
class Post {
final int userId;
final int id;
final String title;
final String body;
Post({this.userId, this.id, this.title, this.body});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
userId: json['userId'],
id: json['id'],
title: json['title'],
body: json['body'],
);
}
}
class MyApp extends StatelessWidget {
final Future<Post> post;
Example MyApp({Key key, this.post}) : super(key: key);
@override
(III) Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
home: Scaffold(
appBar: AppBar(
title: Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Post>(
future: post,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.title);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
),
),
);
}
}
State management
• Flutter rebuilds parts of our UI from scratch
instead of modifying it
– It is fast enough to do that, even on every frame if
needed
• Flutter is declarative, that is, it builds its user
interface to reflect the current state of our app
– A state change triggers a redraw of the user interface
State types
Ephemeral/Local state
• Is the state we can manage in a single widget
• There is no need to serialize it and it does not
change in complex ways
• There is no need to use state management
techniques on this kind of state
• All we need is a StatefulWidget
App state
• We want to share it across many parts of our app,
and we want to keep between user sessions
– User preferences
– Login info
– Notifications in a social networking app
– Shopping cart in an e-commerce app
– Read/unread state of articles in a news app
• We have many options, and the choice depends
on many aspects
A few different options
• Redux
– A state container approach familiar to many web
developers
• BLoC
– A predictable state management library for Dart
• MobX
– A simple, scalable and battle tested state management
solution
• GetX
– A simplified reactive state management solution
Isolates
• Dart’s model for multithreading
• Differs from a conventional thread since it doesn’t
share memory with the main program
• Requires callbacks and a callback dispatcher for
setting up their execution
– Callback handles are managed by the Flutter engine and
can be used to reference and lookup callbacks across
isolates
Test and debugging
• DevTools is a suite of performance and profiling
tools that run in a browser
• Android Studio/IntelliJ and VS Code support a
built-in source-level debugger
• Flutter inspector allows you to examine a visual
representation of the widget tree, inspect
individual widgets and their property values,
enable the performance overlay, and more
Platform integration
• Adding iOS App Clip support
• Apple Watch support
• C and C++ interoperability
• Hosting native Android and iOS views
• Writing platform-specific code
Many more things