Sanjib Sinha - Better Flutter Learn Essential Concepts To Be A Better Flutter Developer-Leanpub - Com (2021)
Sanjib Sinha - Better Flutter Learn Essential Concepts To Be A Better Flutter Developer-Leanpub - Com (2021)
Sanjib Sinha
This book is for sale at http://leanpub.com/betterflutter
Well, we’ll discuss that and many more core concepts regarding this
topic.
But before we start, let’s try to understand what is the difference
between these two terms: mutable and immutable?
Either in object oriented programming, or in functional program-
ming, you cannot change an immutable object’s state once you’ve
created it.
Then how do we change the state of the object? Because that is
important. In flutter, changing object’s state is important. Isn’t it?
From animation to log in, everything involves state.
For more Flutter related Articles and Resources⁴
Mutable or Immutable?
Although widgets are immutable in Flutter, they maintain their
mutability in various ways.
Flutter’ stateful widgets are immutable. However, they store their
mutable state either in State objects. Or in Stream or ChangeNoti-
fier objects to which the state subscribes.
The createState method creates State objects in Flutter.
That’s how we can manage state in Flutter although they have
immutable widgets.
But it has a drawback. The widgets get rebuilt many times in this
process, and it directly impacts your app’s performance.
However, to make flutter app performant they make widgets im-
mutable so we can use identical instances, if the constructor pa-
rameters are same.
⁴https://sanjibsinha.com
1. What is immutable in Flutter? Why Flutter widgets are immutable? 3
1 const SizedBox(
2
3 height: 10.0,
4
5 ),
This is much easier to cache now. So it helps you to make your app
more performant.
Wherever you use the above widget it won’t get rebuilt.
However, apart from Flutter performance, there is a broader spec-
trum around this debate. What should be our choice?
Mutable or Immutable?
1 class Account {
2 int _id;
3 int get id => _id;
4 int set(int id) => id = _id;
5 String nameOfBranch;
6 Account(this._id, this.nameOfBranch);
7 }
8
9 class AccountHolder {
10 String name;
11 String address;
12 int amount;
13 Account account;
14 AccountHolder({this.name, this.account, this.address, thi\
15 s.amount});
16 }
output?
1 class Account {
2 final int id;
3 final String nameOfBranch;
4 const Account({this.id, this.nameOfBranch});
5 }
6
7 class AccountHolder {
8 final String name;
9 final String address;
10 final int amount;
11 final Account account;
12 const AccountHolder({this.name, this.account, this.addres\
13 s, this.amount});
14 }
Now inside our main method we are going to create two immutable
objects. Although these two account holder have the same name,
they have different account id.
Nothing unusual has come out from the above output. Yet we have
tried to know two objects’ state in a different way.
As a result we can clearly see that two different immutable objects’
state are not same. The hash code is a single integer which repre-
sents the state of the object that affects operator == comparisons.
Since the both immutable objects have the same name, their hash
code is same. The state of the name is same for both.
But the biggest disadvantage of an immutable object raises its head
now.
Because we want to change the state of the both immutable objects.
Whatever the reason, we cannot change these two objects any way
since we’ve already created them.
We can only create copy of these two immutable objects again.
When this process of changing takes place if the volume of data
is too big, performance may matter.
One of the main advantages is we can easily find the bug. Im-
mutable objects are better readable, maintainable.
You can pass it around without worry.
In Flutter, it has less memory footprints. So it is performant.
1 secondAccountholder.name == firstCopyOfSecondAccountholde\
2 r.name
3 ? print('Second account holder\'s name unchanged.')
4 : print('Error in second account holder\'s name.');
5 secondAccountholder.account.id == firstCopyOfSecondAccoun\
6 tholder.account.id
7 ? print('Second account holder\'s id unchanged.')
8 : print('Error in second account holder\'s id.');
9
10 firstAccountholder.name.hashCode ==
11 firstCopyOfFirstAccountholder.name.hashCode
12 ? print('true')
13 : print('false');
14
15 /// prints true; that means object state has not been cha\
16 nged
17
18 secondAccountholder.account.id.hashCode ==
19 firstCopyOfSecondAccountholder.account.id.hashCode
20 ? print('true') : print('false');
If we add this part with our last part of our code. We can catch the
error in an easy way.
Now running the code gives us this output:
10
11 false
1 class Account {
2 final int id;
3 final String nameOfBranch;
4 const Account(this.id, this.nameOfBranch);
5 Account copyWith(int id, String nameOfBranch) {
6 return Account(id ?? this.id, nameOfBranch ?? this.nameOf\
7 Branch);
8 }
9 }
10
11 class AccountHolder {
12 final String name;
13 final String address;
14 final int amount;
15 final Account account;
16 const AccountHolder(this.name, this.account, this.address\
17 , this.amount);
18 AccountHolder copyWith(
19 String name, Account account, String address, int amount)\
20 {
1. What is immutable in Flutter? Why Flutter widgets are immutable? 14
1 final firstAccountholder =
2
3 constAccountHolder('John',Account(1,'B. Garden'),'12, ABC\
4 Rd.',1256);
Each time we update the first account holder’s state we can now
use the original state where we need it.
If we want to accomplish more than one goal at a time, we need to
sacrifice.
1. What is immutable in Flutter? Why Flutter widgets are immutable? 15
This build() function returns the root widget, which in turn builds
the UI with the help of lower-level widgets.
The job of Flutter framework is to build those widgets, synchroniz-
ing one widget with another.
This process keeps the process moving on until the process reaches
the low point to represent the ‘RenderObject’, which computes and
gives the representation of the final UI design.
We need to remember that widgets are passed as arguments to other
widgets.
We have already seen how one widget takes a number of different
widgets as named arguments.
For that reason, we will use break our code snippets in different
dart files and finally import them in the ‘main.dart’ file.
We will see the code snippet first, after that we will see how it
affects our UI design. After that we will discuss the widgets used
in our code.
Let us check our first Dart file.
2. Essential Widgets, From Layout to Stateful and Stateless 19
1 // my_appbar.dart
2 import 'package:flutter/material.dart';
3
4 class MyAppBar extends StatelessWidget {
5 MyAppBar({this.title});
6
7 final Widget title;
8
9 @override
10 Widget build(BuildContext context) {
11 return Container(
12 height: 116.0,
13 decoration: BoxDecoration(color: Colors.redAccent),
14
15 child: Row(
16
17 children: <Widget>[
18 IconButton(
19 icon: Icon(Icons.menu),
20 tooltip: 'Navigation menu',
21 onPressed: null,
22 ),
23
24 Expanded(
25 child: title,
26 ),
27 IconButton(
28 icon: Icon(Icons.search),
29 tooltip: 'Search',
30 onPressed: null,
31 ),
32 ],
33 ),
34 );
35 }
2. Essential Widgets, From Layout to Stateful and Stateless 20
36 }
1 //my_scaffold.dart
2 import 'package:flutter/material.dart';
3 import 'package:my_first_flutter_app/chap5_widgets/my_app\
4 bar.dart';
5
6 class MyScaffold extends StatelessWidget {
7 @override
8 Widget build(BuildContext context) {
9
10 return Material(
11
12 child: Column(
13 children: <Widget>[
14 MyAppBar(
15 title: Text(
16 'Test Your Knowledge...',
17 style: Theme.of(context).primaryTextTheme.headline6,
18 ),
19 ),
20 Expanded(
21 child: Center(
22 child: Text('Here we will place our body widget...',
23 style: TextStyle(fontSize: 25),
24 ),
25 ),
26 ),
27 ],
28 ),
29 );
30 }
31 }
2. Essential Widgets, From Layout to Stateful and Stateless 21
And, finally we have the main Dart file through which our applica-
tion will run.
1 //main.dart
2 import 'package:flutter/material.dart';
3 import 'package:my_first_flutter_app/chap5_widgets/my_sca\
4 ffold.dart';
5
6 void main() {
7 runApp(MyFirstApp());
8 }
9
10 class MyFirstApp extends StatelessWidget {
11 @override
12 Widget build(BuildContext context) {
13 return MaterialApp(
14 title: 'My app',
15 home: MyScaffold(),
16 );
17 }
18 }
We have used three different Dart files. While talking about this, we
need to remember that either we can keep these files in ‘lib’ folder.
Or, we can create separate folders inside the ‘lib’ folder, and keep
those files there.
Wherever, we keep this file, while importing we have to mention
the full path.
We will come to that point in a minute, before we need to see how
our widgets build the UI.
2. Essential Widgets, From Layout to Stateful and Stateless 22
1 MyAppBar({this.title});
2 final Widget title;
1 children: <Widget>[]
1 import 'dart:ui';
2
3 import 'package:flutter/material.dart';
4
5 class MaterialDesign extends StatelessWidget {
6 const MaterialDesign({Key? key}) : super(key: key);
7
8 @override
9 Widget build(BuildContext context) {
10 return MaterialApp(
11 title: 'Better Flutter - Essential Widgets',
12 home: MDFirstPage(),
13 initialRoute: '/second',
14 onGenerateRoute: _getSecondPageFirst,
15 );
16 }
17
18 Route<dynamic>? _getSecondPageFirst(RouteSettings setting\
2. Essential Widgets, From Layout to Stateful and Stateless 25
19 s) {
20 if (settings.name != '/second') {
21 return null;
22 }
23
24 return MaterialPageRoute<void>(
25 settings: settings,
26 builder: (BuildContext context) => MDSecondPage(),
27 fullscreenDialog: true,
28 );
29 }
30 }
Watch the above code. Although the home property indicates to the
first page, the initialRoute property navigates to the second page.
However, we need to be careful about one thing. It should return a
non-null value.
The rest is quite simple. Now we can design our first page and
second page.
In any case, the app will open the second page first.
2. Essential Widgets, From Layout to Stateful and Stateless 26
For full code, you can check the GitHub repository for this post, and
related posts.
Before adding custom color in Flutter we need to keep a few things
in mind.
2. Essential Widgets, From Layout to Stateful and Stateless 28
Why we need a custom color? How we can use that custom theme
color throughout an app?
Let me answer the first question first. Because we need a syn-
chronization throughout the app, we need a custom color theme.
Moreover, when the user moves from one screen to the other, she
finds a similarity in theme.
However, the journey starts from the material design and compo-
nents part. And it answers our second question.
The MaterialApp plays the key role here to set the custom color
theme.
1 ThemeData _customTheme() {
2 return ThemeData(
3 accentColor: Color(0xFF442B2D),
4 primaryColor: Color(0xFFFEDBD0),
5 buttonColor: Color(0xFFFEDBD0),
6 scaffoldBackgroundColor: Colors.white,
7 cardColor: Color(0xFF883B2D),
8 textSelectionTheme: TextSelectionThemeData(
9 selectionColor: Color(0xFFFEDBD0),
10 ),
11 errorColor: Colors.red,
12 buttonTheme: ThemeData.light().buttonTheme.copyWith(
13 buttonColor: Color(0xFFFEDBD0),
14 colorScheme: ThemeData.light().colorScheme.copyWith(
2. Essential Widgets, From Layout to Stateful and Stateless 29
15 secondary: Color(0xFF442B2D),
16 ),
17 ),
18 buttonBarTheme: ThemeData.light().buttonBarTheme.copyWith(
19 buttonTextTheme: ButtonTextTheme.accent,
20 ),
21 primaryIconTheme: ThemeData.light().primaryIconTheme.copy\
22 With(
23 color: Color(0xFF442B2D),
24 ),
25 );
26 }
1 theme: _customTheme(),
36 width: 5,
37 color: Theme.of(context).accentColor,
38 ),
39 ),
40 child: Text(
41 'Material Design Custom Theme Page',
42 style: TextStyle(
43 fontSize: 30,
44 fontWeight: FontWeight.bold,
45 color: Theme.of(context).cardColor,
46 ),
47 ),
48 ),
49 Container(
50 margin: EdgeInsets.all(10),
51 padding: EdgeInsets.all(8),
52 child: Card(
53 elevation: 30,
54 shadowColor: Theme.of(context).cardColor,
55 child: Container(
56 margin: EdgeInsets.all(10),
57 padding: EdgeInsets.all(8),
58 child: Column(
59 children: [
60 TextField(
61 decoration: InputDecoration(
62 labelText: 'Username',
63 labelStyle: TextStyle(
64 color: Theme.of(context).primaryColorLight,
65 ),
66 ),
67 ),
68 SizedBox(height: 12.0),
69 TextField(
70 decoration: InputDecoration(
2. Essential Widgets, From Layout to Stateful and Stateless 35
71 labelText: 'Password',
72 labelStyle: TextStyle(
73 color: Theme.of(context).primaryColorLight,
74 ),
75 ),
76 obscureText: true,
77 ),
78 ButtonBar(
79 children: <Widget>[
80 TextButton(
81 child: Text(
82 'CANCEL',
83 style: TextStyle(
84 color: Theme.of(context).buttonColor,
85 ),
86 ),
87 onPressed: () {},
88 ),
89 ElevatedButton(
90 child: Text(
91 'NEXT',
92 style: TextStyle(
93 color: Theme.of(context).buttonColor,
94 ),
95 ),
96 onPressed: () {},
97 ),
98 ],
99 ),
100 ],
101 ),
102 ),
103 ),
104 )
105 ],
2. Essential Widgets, From Layout to Stateful and Stateless 36
106 );
107 }
108 }
As you can see we’ve pressed the button 5 times. And for that
reason, we get this output in our terminal:
1 print _MyHomePageState
2 print CenterWidget
3 print ColumnWidget
4 print _MyHomePageState
5 print CenterWidget
6 print ColumnWidget
7 print _MyHomePageState
8 print CenterWidget
9 print ColumnWidget
10 print _MyHomePageState
11 print CenterWidget
12 print ColumnWidget
13 print _MyHomePageState
14 print CenterWidget
15 print ColumnWidget
16 print _MyHomePageState
17 print CenterWidget
18 print ColumnWidget
There are six sets, because whenever you run the Flutter app, the
setState() calls the build() method and it displays the number 0 on
the screen.
Next, each time you press the button, it rebuilds the descendant
widget trees also. If we quit the app and restarts the app again, it
starts with a fresh display of a screen.
Figure 2.5 – Flutter hot restart rebuilds the widget tree once
Now it makes sense why we have added a print() method with each
build() method of each widget.
Every time the setState() method in the state full widget calls the
build() method, the descendant widget trees’ build() method also
rebuilds the associated widget.
2. Essential Widgets, From Layout to Stateful and Stateless 40
Now we can take a look at the simple code snippet, that we have
used to demonstrate the flutter rebuilding inner mechanism.
1 import 'package:flutter/material.dart';
2
3 void main() {
4 runApp(MyApp());
5 }
6
7 class MyApp extends StatelessWidget {
8 // This widget is the root of your application.
9 @override
10 Widget build(BuildContext context) {
11 return MaterialApp(
12 title: 'Flutter Demo',
13 theme: ThemeData(
14
15 primarySwatch: Colors.blue,
16 ),
17 home: MyHomePage(title: 'Flutter Demo Home Page'),
18 );
19 }
20 }
21
22 class MyHomePage extends StatefulWidget {
23 MyHomePage({Key? key, this.title}) : super(key: key);
24
25 final String? title;
26
27 @override
28 _MyHomePageState createState() => _MyHomePageState();
29 }
30
31 class _MyHomePageState extends State<MyHomePage> {
32 int _counter = 0;
33
2. Essential Widgets, From Layout to Stateful and Stateless 41
34 void _incrementCounter() {
35 setState(() {
36
37 _counter++;
38 });
39 }
40
41 @override
42 Widget build(BuildContext context) {
43
44 title: Text(widget.title!),
45 ),
46 body: CenterWidget(
47 counter: _counter,
48 ),
49 floatingActionButton: FloatingActionButton(
50 onPressed: _incrementCounter,
51 tooltip: 'Increment',
52 child: Icon(Icons.add),
53 ), // This trailing comma makes auto-formatting nicer for\
54 build methods.
55 );
56 }
57 }
58
59 class CenterWidget extends StatelessWidget {
60
61 const CenterWidget({
62 Key? key,
63 required this.counter,
64 }) : super(key: key);
65
66 final counter;
67
68 @override
2. Essential Widgets, From Layout to Stateful and Stateless 42
Although Flutter is all about widgets, and no doubt, by and large it’s
true, yet we shouldn’t forget about the element tree and rendered
object tree that Flutter manages under the hood.
Each time we call the setState() method, it reruns the build()
method. Flutter optimizes this rerunning process, and does that
with the help of element and rendered object trees that we don’t
see.
However, we can see the build() rerun process with the help of
print() method,which we’ve exactly done.
With reference to the state object we can say, setState() tells the
Flutter framework that something has changed in this State. This
change fires the build() method to rerun. Moreover, it reflects the
change on the screen.
The display can now reflect the change on the screen.
For full code for this part please visit my GitHub repository⁷
As you can see, we have positioned both the Text widgets applying
some styles.
How did we do that?
2. Essential Widgets, From Layout to Stateful and Stateless 46
1 import 'package:flutter/material.dart';
2
3 class AddNameList extends StatefulWidget {
4 @override
5 _AddNameListState createState() => _AddNameListState();
6 }
7
8 class _AddNameListState extends State<AddNameList> {
9 final textController = TextEditingController();
10 @override
11 Widget build(BuildContext context) {
12 return Scaffold(
13 appBar: AppBar(
14 title: Text('Add Expenditures'),
15 ),
16 body: Center(
17 child: ListView(
18 children: [
19 Container(
20 margin: EdgeInsets.all(8),
21 padding: EdgeInsets.all(8),
22 decoration: BoxDecoration(
23 color: Colors.blue[50],
24 border: Border.all(
2. Essential Widgets, From Layout to Stateful and Stateless 47
25 color: Colors.redAccent,
26 ),
27 ),
28 alignment: Alignment.topCenter,
29 child: const Text(
30 'I am at the top and positioned center.',
31 style: TextStyle(
32 fontWeight: FontWeight.bold,
33 fontSize: 20,
34 ),
35 ),
36 ),
37 Container(
38 margin: EdgeInsets.all(8),
39 padding: EdgeInsets.all(8),
40 decoration: BoxDecoration(
41 color: Colors.blue[100],
42 border: Border.all(
43 color: Colors.black45,
44 width: 8,
45 ),
46 ),
47 alignment: Alignment.bottomLeft,
48 child: const Text(
49 'I am at the bottom and positioned left.',
50 style: TextStyle(
51 fontWeight: FontWeight.bold,
52 fontSize: 20,
53 ),
54 ),
55 ),
56 const SizedBox(
57 height: 8,
58 ),
59 ],
2. Essential Widgets, From Layout to Stateful and Stateless 48
60 ),
61 ),
62 );
63 }
64 }
For the full application code snippets please visit the GitHub
repository of the related post.
1 Container(
2 margin: EdgeInsets.all(8),
3 padding: EdgeInsets.all(8),
4 decoration: BoxDecoration(
5 color: Colors.blue[50],
6 border: Border.all(
7 color: Colors.redAccent,
8 ),
9 ),
10 alignment: Alignment.topCenter,
11 child: const Text(
12 'I am at the top and positioned center.',
13 style: TextStyle(
14 fontWeight: FontWeight.bold,
15 fontSize: 20,
16 ),
17 ),
18 ),
2. Essential Widgets, From Layout to Stateful and Stateless 49
1 Container(
2 margin: EdgeInsets.all(8),
3 padding: EdgeInsets.all(8),
4 decoration: BoxDecoration(
5 color: Colors.blue[100],
6 border: Border.all(
7 color: Colors.black45,
8 width: 8,
9 ),
10 ),
11 alignment: Alignment.bottomLeft,
12 child: const Text(
13 'I am at the bottom and positioned left.',
14 style: TextStyle(
15 fontWeight: FontWeight.bold,
16 fontSize: 20,
17 ),
18 ),
19 ),
Not only we have changed the alignment, but along with that we
have added a border and changed the color of the container.
For that reason, it looks different, now.
2. Essential Widgets, From Layout to Stateful and Stateless 50
10 Size? fixedSize,
11 BorderSide? side,
12 OutlinedBorder? shape,
13 MouseCursor? enabledMouseCursor,
14 MouseCursor? disabledMouseCursor,
15 VisualDensity? visualDensity,
16 MaterialTapTargetSize? tapTargetSize,
17 Duration? animationDuration,
18 bool? enableFeedback,
19 AlignmentGeometry? alignment,
20 InteractiveInkFeatureFactory? splashFactory,
21 })
1 import 'package:flutter/material.dart';
2
3 class ContainerRowColumnModified extends StatelessWidget {
4 const ContainerRowColumnModified({Key, key}) : super(key:\
5 key);
6
7 @override
8 Widget build(BuildContext context) {
9 return ListView(
10 children: [
11 Container(
12 margin: EdgeInsets.all(8),
13 padding: EdgeInsets.all(15),
14 alignment: Alignment.topCenter,
15 decoration: BoxDecoration(
16 color: Colors.purple,
17 border: Border.all(
18 width: 5,
19 color: Colors.grey,
20 ),
21 ),
22 child: Column(
23 mainAxisAlignment: MainAxisAlignment.center,
24 children: [
25 Card(
26 child: Column(
27 mainAxisSize: MainAxisSize.min,
28 children: <Widget>[
29 const ListTile(
30 leading: Icon(Icons.album),
31 title: Text('Books'),
32 subtitle: Text('25.00'),
33 ),
34 const ListTile(
35 leading: Icon(Icons.album),
2. Essential Widgets, From Layout to Stateful and Stateless 54
36 title: Text('Groceries'),
37 subtitle: Text('250.00'),
38 ),
39 const ListTile(
40 leading: Icon(Icons.album),
41 title: Text('Fruits'),
42 subtitle: Text('480.00'),
43 ),
44 Wrap(
45 alignment: WrapAlignment.spaceBetween,
46 direction: Axis.horizontal,
47 children: <Widget>[
48 Container(
49 margin: const EdgeInsets.all(8),
50 child: ElevatedButton(
51 onPressed: () => {},
52 child: Text(' ADD '),
53 style: ElevatedButton.styleFrom(
54 primary: Colors.purple,
55 padding: EdgeInsets.symmetric(
56 horizontal: 20, vertical: 20),
57 textStyle: TextStyle(
58 fontSize: 16, fontWeight: FontWeight.bold),
59 ),
60 ),
61 ),
62 Container(
63 margin: const EdgeInsets.all(8),
64 child: ElevatedButton(
65 onPressed: () => {},
66 child: Text(' UPDATE '),
67 style: ElevatedButton.styleFrom(
68 primary: Colors.purple,
69 padding: EdgeInsets.symmetric(
70 horizontal: 20, vertical: 20),
2. Essential Widgets, From Layout to Stateful and Stateless 55
71 textStyle: TextStyle(
72 fontSize: 16, fontWeight: FontWeight.bold),
73 ),
74 ),
75 ),
76 Container(
77 margin: const EdgeInsets.all(8),
78 child: ElevatedButton(
79 onPressed: () => {},
80 child: Text(' DELETE '),
81 style: ElevatedButton.styleFrom(
82 primary: Colors.purple,
83 padding: EdgeInsets.symmetric(
84 horizontal: 20, vertical: 20),
85 textStyle: TextStyle(
86 fontSize: 16, fontWeight: FontWeight.bold),
87 ),
88 ),
89 ),
90 Container(
91 margin: const EdgeInsets.all(8),
92 child: ElevatedButton(
93 onPressed: () => {},
94 child: Text(' NEXT PAGE '),
95 style: ElevatedButton.styleFrom(
96 primary: Colors.purple,
97 padding: EdgeInsets.symmetric(
98 horizontal: 20, vertical: 20),
99 textStyle: TextStyle(
100 fontSize: 16, fontWeight: FontWeight.bold),
101 ),
102 ),
103 ),
104 ],
105 ),
2. Essential Widgets, From Layout to Stateful and Stateless 56
106 ],
107 ),
108 ),
109 ],
110 ),
111 ),
112 ],
113 );
114 }
115 }
1 Container(
2 margin: const EdgeInsets.all(8),
3 child: ElevatedButton(
4 onPressed: () => {},
5 child: Text(' UPDATE '),
6 style: ElevatedButton.styleFrom(
7 primary: Colors.purple,
8 padding: EdgeInsets.symmetric(
9 horizontal: 20, vertical: 20),
10 textStyle: TextStyle(
11 fontSize: 16, fontWeight: FontWeight.bold),
12 ),
13 ),
14 ),
2. Essential Widgets, From Layout to Stateful and Stateless 57
Let us start with the container widget first, and check how we can
explore its characteristics.
Most importantly, a container widget must have a child widget. As
a result, a container widget in flutter can have a column as its child
widget.
On the other hand, it could have a row as its child widget. Conse-
quently, inside a container, a column can have several containers
as its children, or those containers can have a row as its child again.
As a result, app design might turn into a complex one.
With the help of different types of images, we will try to understand
how container widget works, and at the same time we’ll understand
the difference between container and column, row.
The main characteristic of container widget can control the custom
styling and alignment. For instance you can align it to left or right,
2. Essential Widgets, From Layout to Stateful and Stateless 58
adding some color and border the container with different color,
like that.
Most importantly, container’s rich alignment, styling options and
flexible width let us design our flutter app in a better way.
On the other hand, row and column, both can take multiple, in fact,
unlimited child widgets. In conclusion, their children return a list
of widgets. As a result, we can map a list there.
However, there are some styling limitations that we must handle.
We can align a column, or row; but we cannot add some other
styling options that are available in a container widget.
Above all, we have to use Column and Row when we want to sit
widgets, either above, or next to each other. The column widget
takes the full available height. Meanwhile, a row widget takes the
full available width.
Since container must take exactly one child widget, it can also have
children widgets. For example, a container widget have a column
as its child widget, so that column can have unlimited widgets as
its children.
Moreover, they will act as the container’s descendants.
Consider the following code snippets.
2. Essential Widgets, From Layout to Stateful and Stateless 59
1 import 'package:flutter/material.dart';
2
3 class ContainerColumnRow extends StatelessWidget {
4 const ContainerColumnRow({Key, key}) : super(key: key);
5
6 @override
7 Widget build(BuildContext context) {
8 return MaterialApp(
9 title: 'Difference between container, column and row',
10 home: CCRFirstPage(),
11 );
12 }
13 }
14
15 class CCRFirstPage extends StatelessWidget {
16 const CCRFirstPage({Key, key}) : super(key: key);
17
18 @override
19 Widget build(BuildContext context) {
20 return Scaffold(
21 appBar: AppBar(
22 title: Text('Container, Column and Row first page'),
23 ),
24 body: CCRFirstPageBody(),
25 );
26 }
27 }
28
29 class CCRFirstPageBody extends StatelessWidget {
30 const CCRFirstPageBody({Key, key}) : super(key: key);
31
32 @override
33 Widget build(BuildContext context) {
34 return Container(
35 margin: EdgeInsets.all(8),
2. Essential Widgets, From Layout to Stateful and Stateless 60
36 padding: EdgeInsets.all(15),
37 decoration: BoxDecoration(
38 color: Colors.purple,
39 ),
40 child: Column(
41 mainAxisAlignment: MainAxisAlignment.start,
42 children: [
43 Card(
44 child: Column(
45 mainAxisSize: MainAxisSize.min,
46 children: <Widget>[
47 const ListTile(
48 leading: Icon(Icons.album),
49 title: Text('Books'),
50 subtitle: Text('25.00'),
51 ),
52 const ListTile(
53 leading: Icon(Icons.album),
54 title: Text('Groceries'),
55 subtitle: Text('250.00'),
56 ),
57 const ListTile(
58 leading: Icon(Icons.album),
59 title: Text('Fruits'),
60 subtitle: Text('480.00'),
61 ),
62 Row(
63 mainAxisAlignment: MainAxisAlignment.end,
64 children: <Widget>[
65 TextButton(
66 child: const Text('ADD TO LIST'),
67 onPressed: () {/* ... */},
68 ),
69 const SizedBox(width: 8),
70 TextButton(
2. Essential Widgets, From Layout to Stateful and Stateless 61
We have shown the image, therefore we can take a look at the code
first.
2. Essential Widgets, From Layout to Stateful and Stateless 68
1 import 'package:flutter/material.dart';
2
3 class ContainerRowColumnModified extends StatelessWidget {
4 const ContainerRowColumnModified({Key, key}) : super(key:\
5 key);
6
7 @override
8 Widget build(BuildContext context) {
9 return ListView(
10 children: [
11 Container(
12 margin: EdgeInsets.all(8),
13 padding: EdgeInsets.all(15),
14 alignment: Alignment.topCenter,
15 decoration: BoxDecoration(
16 color: Colors.purple,
17 border: Border.all(
18 width: 5,
19 color: Colors.grey,
20 ),
21 ),
22 child: Column(
23 mainAxisAlignment: MainAxisAlignment.center,
24 children: [
25 Card(
26 child: Column(
27 mainAxisSize: MainAxisSize.min,
28 children: <Widget>[
29 const ListTile(
30 leading: Icon(Icons.album),
31 title: Text('Books'),
32 subtitle: Text('25.00'),
33 ),
34 const ListTile(
35 leading: Icon(Icons.album),
2. Essential Widgets, From Layout to Stateful and Stateless 69
36 title: Text('Groceries'),
37 subtitle: Text('250.00'),
38 ),
39 const ListTile(
40 leading: Icon(Icons.album),
41 title: Text('Fruits'),
42 subtitle: Text('480.00'),
43 ),
44 Wrap(
45 alignment: WrapAlignment.spaceBetween,
46 direction: Axis.horizontal,
47 children: <Widget>[
48 Container(
49 margin: const EdgeInsets.all(8),
50 child: ElevatedButton(
51 onPressed: () => {},
52 child: Text(' ADD '),
53 style: ElevatedButton.styleFrom(
54 primary: Colors.purple,
55 padding: EdgeInsets.symmetric(
56 horizontal: 20, vertical: 20),
57 textStyle: TextStyle(
58 fontSize: 16, fontWeight: FontWeight.bold),
59 ),
60 ),
61 ),
62 Container(
63 margin: const EdgeInsets.all(8),
64 child: ElevatedButton(
65 onPressed: () => {},
66 child: Text(' UPDATE '),
67 style: ElevatedButton.styleFrom(
68 primary: Colors.purple,
69 padding: EdgeInsets.symmetric(
70 horizontal: 20, vertical: 20),
2. Essential Widgets, From Layout to Stateful and Stateless 70
71 textStyle: TextStyle(
72 fontSize: 16, fontWeight: FontWeight.bold),
73 ),
74 ),
75 ),
76 Container(
77 margin: const EdgeInsets.all(8),
78 child: ElevatedButton(
79 onPressed: () => {},
80 child: Text(' DELETE '),
81 style: ElevatedButton.styleFrom(
82 primary: Colors.purple,
83 padding: EdgeInsets.symmetric(
84 horizontal: 20, vertical: 20),
85 textStyle: TextStyle(
86 fontSize: 16, fontWeight: FontWeight.bold),
87 ),
88 ),
89 ),
90 Container(
91 margin: const EdgeInsets.all(8),
92 child: ElevatedButton(
93 onPressed: () => {},
94 child: Text(' NEXT PAGE '),
95 style: ElevatedButton.styleFrom(
96 primary: Colors.purple,
97 padding: EdgeInsets.symmetric(
98 horizontal: 20, vertical: 20),
99 textStyle: TextStyle(
100 fontSize: 16, fontWeight: FontWeight.bold),
101 ),
102 ),
103 ),
104 ],
105 ),
2. Essential Widgets, From Layout to Stateful and Stateless 71
106 ],
107 ),
108 ),
109 ],
110 ),
111 ),
112 ],
113 );
114 }
115 }
While we are using the Wrap widget to adjust the child widgets
sitting next to each other, we’ve done another important thing.
Further, we have changed the color of the ElevatedButton. By
default it takes the blue color. But, we can change the color to our
choice.
At the same time, we can adjust the position.
The code repository for this chapter is also available at⁸
For more Flutter and Dart related articles please visit⁹
Just click on [email protected] to send me an email.
⁸https://github.com/sanjibsinha/better_flutter/tree/main/lib/chapter_two
⁹https://sanjibsinha.com/
3. Inherited Widget,
Provider and State
Management in Flutter
Why state management in Flutter is one of the most important
topics? What is flutter state?
Well, state is the information that a user can read synchronously
with the change in widget. During the lifetime of the widget, it can
hold that data.
Even if you refresh or render the widget, the data stays.
Now, there are different types of approaches to manage state.
This is the most basic approach. Moreover, in this tutorial, we will
see how a widget can manage the state and renders the state to
itself. Why do we need state management?
To begin with, I must clarify one thing. State is an object. It is not
a widget. Although in Flutter, everything is widget.
Therefore, we can say that a StatefulWidget is actually immutable,
it is StatelessWidget. The State of the widget is managed by the
State object.
However, that is not the key point here.
We’ll see how a widget can manage its own state.
There are a few steps that we need to do before we proceed.
We’ll create a class OwnStateManagingWidget. And we’ll manages
state for OwnStateManagingWidget.
Next, we’ll define the _stateChanged boolean which determines the
box’s current color.
3. Inherited Widget, Provider and State Management in Flutter 73
1 import 'package:flutter/material.dart';
2
3 class OwnStateManagingWidget extends StatefulWidget {
4 @override
5 _OwnStateManagingWidgetState createState() => _OwnStateMa\
6 nagingWidgetState();
7 }
8
9 class _OwnStateManagingWidgetState extends State<OwnState\
10 ManagingWidget> {
11 /// let's define the boolean value first
12 ///
13
14 bool _stateChanged = false;
15
16 /// let's create a function that will define the setState\
17 () method
¹⁰https://sanjibsinha.com
3. Inherited Widget, Provider and State Management in Flutter 74
Once the user taps the red box, it calls the setState() function to
update the UI and make the box color to green.
16 _inActive = newValue;
17 });
18 }
19
20 @override
21 Widget build(BuildContext context) {
22 return ChildWidget(
23 inActive: _inActive,
24 notifyParent: _manageStateForChildWidget,
25 );
26 }
27 }
The code is very straight forward. By default the state object should
be inactive. So we’ve made it true. It also manages the state of the
child widget.
As we tap a box, it will no longer remain inactive. It becomes false,
from true and makes the state active.
So we need the setState() method, that will implement a method,
which in turn,passes a boolean parameter whose value is false.
Watch this part of the above code:
23 child: Text(
24 inActive ? 'Inactive' : 'Active',
25 style: TextStyle(
26 fontSize: 25.0,
27 color: Colors.white,
28 ),
29 ),
30 ),
31 width: 250.0,
32 height: 250.0,
33 decoration: BoxDecoration(color: inActive ? Colors.red : \
34 Colors.green),
35 ),
36 );
37 }
38 }
1 ValueChanged<bool>.
adds interactivity to any Flutter app in a safe way, but while doing
so it redraws every widget above it in the widget tree.
We don’t prefer that approach. For a small application we can use
that approach though.
1 import 'package:flutter/material.dart';
2 import 'controller/inherited-widget/inherited_widget_on_t\
3 op.dart';
4
5 main() => runApp(OurApp());
6
7 class OurApp extends StatelessWidget {
8 @override
9 Widget build(BuildContext context) {
10 return MaterialApp(
11 title: 'Our App',
12 debugShowCheckedModeBanner: false,
13 home: Scaffold(
14 body: InheritedWidgetOnTop(),
15 ),
16 );
17 }
18 }
1 import 'package:flutter/material.dart';
2 import 'package:our_app/controller/inherited-widget/widge\
3 ts-lists/widgets_lists.dart';
4
5 class InheritedWidgetOnTop extends StatefulWidget {
6 @override
7 _InheritedWidgetOnTopState createState() => _InheritedWid\
8 getOnTopState();
9 }
10
11 class _InheritedWidgetOnTopState extends State<InheritedW\
12 idgetOnTop> {
13 @override
14 Widget build(BuildContext context) {
15 return ListView(
16 padding: const EdgeInsets.all(30.0),
17 children: [
18 EyeColor(
19 color: Colors.deepOrange,
20 child: Builder(builder: (BuildContext innerContext) {
21 return GrandParent();
22 })),
23 SizedBox(
24 height: 10.0,
25 ),
26 ChangingAge(
27 age: new ChangeAge(age: 25),
28 child: Builder(builder: (BuildContext textContext) {
29 return UncleClasses();
30 })),
31 ],
32 );
33 }
34 }
1 controller/inherited-widget/widgets-lists/widgets_lists.d\
2 art
1 import 'package:flutter/material.dart';
2
3 class EyeColor extends InheritedWidget {
4 const EyeColor({
5 Key key,
6 @required this.color,
7 @required Widget child,
8 }) : assert(color != null),
9 assert(child != null),
10 super(key: key, child: child);
11
12 final Color color;
13
14 static EyeColor of(BuildContext context) {
15 return context.dependOnInheritedWidgetOfExactType<EyeColo\
16 r>();
17 }
18
19 @override
20 bool updateShouldNotify(EyeColor old) => color != old.col\
21 or;
22 }
23
24 class ChangingAge extends InheritedWidget {
25 const ChangingAge({
26 Key key,
27 @required this.age,
28 @required Widget child,
29 }) : assert(age != null),
30 super(key: key, child: child);
31
32 final ChangeAge age;
33
34 static ChangingAge of(BuildContext context) {
35 return context.dependOnInheritedWidgetOfExactType<Changin\
3. Inherited Widget, Provider and State Management in Flutter 87
36 gAge>();
37 }
38
39 @override
40 bool updateShouldNotify(ChangingAge old) => age != old.ag\
41 e;
42 }
43
44 class ChangeAge {
45 int age;
46 ChangeAge({this.age});
47 void changeAge() {
48 age += 5;
49 }
50 }
1 return context.dependOnInheritedWidgetOfExactType<Changin\
2 gAge>();
It allows the class to create its own fallback logic. It is important for
one reason. There could be other widgets who are not consumers
and they are not in the scope.
3. Inherited Widget, Provider and State Management in Flutter 88
Now the “of” method may return any type of data. In our app,
the inherited widget “EyeColor” returns Flutter Color class. The
consumer widgets of inherited widget EyeColor
The Grandparent is the main consumer, and it has a descendant
FatherClass. Let us close watch them first.
30 Text(
31 'I am the Father. I have two brothers.',
32 style: TextStyle(
33 color: EyeColor.of(context).color,
34 fontSize: 30.0,
35 fontWeight: FontWeight.bold),
36 ),
37 ],
38 );
39 }
40 }
1 color: eyeColor,
The main consumer has two more consumer widgets inside it.
FirstUncleClass and UncleClass.
Let us take a look at them too.
3. Inherited Widget, Provider and State Management in Flutter 91
36 }
37 }
1 FloatingActionButton(
2 onPressed: () {
3 setState(() {
4 secondUncleAge.changeAge();
5 });
6 },
7 child: Icon(Icons.add),
8 backgroundColor: Colors.blue,
9 ),
10 UnclesChildClass(),
The context points to the exact position where any Widget stays
at the Widget tree. We’ll discuss this topic separatly in another
Chapter.
1 // pubspec.yaml
2
3 dependencies:
4 flutter:
5 sdk: flutter
6
7 provider:
Now you are set to use Provider to manage state in your app.
What is state? Well, state is something that exists on memory. When
your app is running, in most cases you will try to manage state.
Provider helps you to do that.
State is extremely important. Managing it efficiently is important
too.
3. Inherited Widget, Provider and State Management in Flutter 94
1 import 'package:basic_flutter_provider/models/counting_th\
2 e_number.dart';
3 import 'package:basic_flutter_provider/views/my_home_page\
4 .dart';
5 import 'package:flutter/material.dart';
6 import 'package:provider/provider.dart';
7
8 void main() {
9 runApp(
10 ChangeNotifierProvider(
11 create: (context) =>
12 CountingTheNumber(),
13 child: MyApp(),
3. Inherited Widget, Provider and State Management in Flutter 95
14 ),
15 );
16 }
17
18 class MyApp extends StatelessWidget {
19 // This widget is the root of your application.
20 @override
21 Widget build(BuildContext context) {
22 return MaterialApp(
23 title: 'Flutter Demo',
24 theme: ThemeData(
25 primarySwatch: Colors.blue,
26 ),
27 home: MyHomePage(),
28 );
29 }
30 }
It creates a context that returns the model from where the data
comes. And it also returns a child widget, which will consume the
data.
For more Flutter related Articles and Resources¹³
For a simple reason. First of all it will store and give us changed data.
Although in this example the data is ephemeral, or short-lived, still
we need a small model class.
¹³https://sanjibsinha.com
3. Inherited Widget, Provider and State Management in Flutter 96
1 import 'package:flutter/widgets.dart';
2
3 class CountingTheNumber with ChangeNotifier {
4 int number = 0;
5 void increaseNumber() {
6 number++;
7 notifyListeners();
8 }
9 }
1 import 'package:basic_flutter_provider/controllers/a_very\
2 _deep_widget_tree.dart';
3
4 import 'package:flutter/material.dart';
5
6 class MyHomePage extends StatelessWidget {
7 @override
8 Widget build(BuildContext context) {
9 return Scaffold(
10 appBar: AppBar(
11 title: Text('Basic Provider Explained to Beginners'),
12 ),
13 body: Center(
14 child: AVeryDeepWidgetTree(),
15 // This trailing comma makes auto-formatting nicer for bu\
16 ild methods.
17 ));
18 }
19 }
1 import 'package:basic_flutter_provider/models/countin\
2 g_the_number.dart';
3 import 'package:flutter/material.dart';
4 import 'package:provider/provider.dart';
5
6 class AVeryDeepWidgetTree extends StatelessWidget {
7 @override
8 Widget build(BuildContext context) {
9 // ‘Provider.of’, just like Consumer needs to know th\
10 e type of the model.
11 //We need to specify the model ‘CountingTheNumber’.
12 final counter = Provider.of<CountingTheNumber>(contex\
13 t);
14 return Container(
15 padding: const EdgeInsets.all(20.0),
16 child: Column(
17 mainAxisAlignment: MainAxisAlignment.center,
18 children: <Widget>[
19 Text(
20 'This is a simple Text widget',
21 style: TextStyle(
22 color: Colors.black,
23 fontSize: 45.0,
24 fontWeight: FontWeight.bold,
25 ),
26 ),
3. Inherited Widget, Provider and State Management in Flutter 98
62 tooltip: 'Increment',
63 child: Icon(Icons.add),
64 ),
65 ],
66 ),
67 ),
68 ),
69 ],
70 ),
71 );
72 }
73 }
Remember our model class. We have only one variable that will
reflect the state change. So at the bottom-most widget we need to
access it.
To do that, we will use Provider.of() method. Where we should
mention the type of our model, and pass the context.
The Provider helps us to access the model data and method any-
where inside that widget tree.
1 Text(
2 '${counter.number}',
3 style: TextStyle(fontSize: 25.0),
4 ),
5 SizedBox(
6 height: 5.0,
7 ),
8 FloatingActionButton(
9 onPressed: () {
10 counter.increaseNumber();
3. Inherited Widget, Provider and State Management in Flutter 100
11 },
12 tooltip: 'Increment',
13 child: Icon(Icons.add),
14 ),
Clicking the button will change the state of the app by increasing
the number.
The main drawback of the above code hides itself in this line of
Code:
We want to rebuild only the text part where the number will show
itself. And we want to rebuild the floating action button section.
Achieving that is easy. All we need to write a separate widget for
those parts. After that we will call it inside our view.
1 import 'package:flutter/material.dart';
2 import 'package:flutter_provider_explained_for_beginners/\
3 model/counting_the_number.dart';
4 import 'package:provider/provider.dart';
5
6 class ColumnClass extends StatelessWidget {
7 @override
8 Widget build(BuildContext context) {
9 // ‘Provider.of’, just like Consumer needs to know the ty\
10 pe of the model.
11 // We need to specify the model ‘CountingTheNumber’.
12 //this time only this widget will be rebuilt
13 final CountingTheNumber counter = Provider.of<CountingThe\
14 Number>(context);
15 return Column(
16 children: [
3. Inherited Widget, Provider and State Management in Flutter 101
17 Text(
18 '${counter.number}',
19 style: TextStyle(fontSize: 25.0),
20 ),
21 SizedBox(height: 10.0),
22 FloatingActionButton(
23 onPressed: () {
24 counter.increaseNumber();
25 },
26 tooltip: 'Increment',
27 child: Icon(Icons.add),
28 )
29 ],
30 );
31 }
32 }
That’s all. Running the code will only change a very small segment
when we press the button and change the state.
We have successfully removed the pitfalls of rebuilding the whole
widget tree. What is flutter provider? How does provider flutter
work?
The change of state affects a small segment of widget.
3. Inherited Widget, Provider and State Management in Flutter 102
1 import 'package:provider/provider.dart';
We don’t want that. Flutter’s default State object rebuilds the whole
widget tree. While managing state in the deepest widget, we cannot
allow this to happen.
Suppose at the bottom of widget tree, inside any Text widget we
want to reflect our state change.
To do that, we cannot allow the top widgets to rebuild themselves.
What is the solution?
The solution is Provider and Consumer. It starts with ChangeNoti-
fierProvider.
ChangeNotifierProvider is the widget that provides an instance of
a ChangeNotifier to its descendants. It comes from the provider
package.
We should keep it at the topmost place of our widget tree. Why
should we do this?
Let us try to understand the mechanism of state management in
Flutter, first. Flutter is a declarative framework. If we want to
change the state of UI, we should rebuild it.
Suppose we want to change the bottom-most widget. We cannot do
that imperatively from outside, by calling a method on it.
Is there any way so that we can let Flutter help us?
Yes, there is.
We don’t want to fight with it, or force it to adopt something that
goes against its nature. On the contrary, we will take help from
Flutter to do that heavy lifting.
1 import 'package:flutter/widgets.dart';
2
3 class CountingTheNumber with ChangeNotifier {
4 int number = 0;
5 String message = 'Sanjib Sinha';
6
7 void increaseNumber(int number) {
8 number++;
9 notifyListeners();
10 }
11
12 void testMessage() {
13 message.startsWith('S')
14 ? message = 'Hi Sanjib'
15 : message = 'First letter is not S';
16 notifyListeners();
17 }
18 }
1 import 'package:flutter/material.dart';
2 import 'package:flutter_provider_explained_for_beginners/\
3 model/counting_the_number.dart';
4 import 'package:flutter_provider_explained_for_beginners/\
5 view/my_home_page.dart';
6 import 'package:provider/provider.dart';
7
8 void main() {
9 runApp(
10 // ChangeNotifierProvider, unlike ChangeNotifier, comes f\
11 rom the Provider package
12 // and it provides an instance of a ChangeNotifier to the\
13 widgets,
14 // which have already subscribed to it
15 // we should place the ChangeNotifierProvider Just above \
16 the widgets that need to access it.
17 // you will understand provider better if you already hav\
18 e understood how
19 // InheritedWidget works
20 ChangeNotifierProvider(
21 create: (context) =>
22 CountingTheNumber(), // designed Model is provided to the\
23 desired widgets
24 child: MyApp(),
25 ),
26 );
27 }
28
29 class MyApp extends StatelessWidget {
30 // This widget is the root of your application.
31 @override
32 Widget build(BuildContext context) {
33 return MaterialApp(
34 title: 'Flutter Demo',
3. Inherited Widget, Provider and State Management in Flutter 107
35 theme: ThemeData(
36 primarySwatch: Colors.blue,
37 ),
38 home: MyHomePage(),
39 );
40 }
41 }
1 import 'package:flutter/material.dart';
2 import 'package:flutter_provider_explained_for_beginners/\
3 model/counting_the_number.dart';
4 import 'package:provider/provider.dart';
5
6 class ColumnClass extends StatelessWidget {
7 @override
8 Widget build(BuildContext context) {
9
10 /// we're using Consumer widget instead of Provider.of().
11 /// we've put our Consumer widget as deep as possible in \
12 the tree
13 return Column(
14 children: [
15 Container(
16 margin: const EdgeInsets.all(
17 5.0,
18 ),
19 child: Consumer<CountingTheNumber>(
20 builder: (context, message, child) {
21 return Column(
22 children: [
23 child,
24 Text(
25 '${message.message}',
26 style: TextStyle(fontSize: 25.0),
27 ),
28 ],
29 );
30 },
31
32 /// building a humongous widget tree
33 child: Row(
34 mainAxisAlignment: MainAxisAlignment.center,
35 children: [
3. Inherited Widget, Provider and State Management in Flutter 109
36 Column(
37 children: [
38 Text(
39 'First Row',
40 style: TextStyle(
41 fontSize: 20.0,
42 color: Colors.blue,
43 ),
44 ),
45 SizedBox(
46 height: 10.0,
47 ),
48 Text(
49 'Second Row',
50 style: TextStyle(
51 fontSize: 20.0,
52 color: Colors.red,
53 ),
54 ),
55 ],
56 ),
57 const Divider(
58 color: Colors.black,
59 height: 20,
60 thickness: 5,
61 indent: 20,
62 endIndent: 0,
63 ),
64 Column(
65 children: [
66 Text(
67 'First Row',
68 style: TextStyle(
69 fontSize: 20.0,
70 color: Colors.red,
3. Inherited Widget, Provider and State Management in Flutter 110
71 ),
72 ),
73 SizedBox(
74 height: 10.0,
75 ),
76 Text(
77 'Second Row',
78 style: TextStyle(
79 fontSize: 20.0,
80 color: Colors.blue,
81 ),
82 ),
83 ],
84 ),
85 ],
86 ),
87 ),
88 ),
89 SizedBox(height: 10.0),
90 Container(
91 margin: const EdgeInsets.all(
92 5.0,
93 ),
94 child: Consumer<CountingTheNumber>(
95 builder: (context, message, child) {
96 return Column(
97 children: [
98 FloatingActionButton(
99 onPressed: () {
100 message.testMessage();
101 },
102 tooltip: 'Increment',
103 child: Icon(Icons.ac_unit_rounded),
104 ),
105 child,
3. Inherited Widget, Provider and State Management in Flutter 111
106 ],
107 );
108 },
109
110 /// building another humongous widget tree
111 child: Row(
112 mainAxisAlignment: MainAxisAlignment.center,
113 children: [
114 Column(
115 children: [
116 Text(
117 'First Row',
118 style: TextStyle(
119 fontSize: 20.0,
120 color: Colors.blue,
121 ),
122 ),
123 SizedBox(
124 height: 10.0,
125 ),
126 Text(
127 'Second Row',
128 style: TextStyle(
129 fontSize: 20.0,
130 color: Colors.red,
131 ),
132 ),
133 ],
134 ),
135 const Divider(
136 color: Colors.black,
137 height: 20,
138 thickness: 5,
139 indent: 20,
140 endIndent: 0,
3. Inherited Widget, Provider and State Management in Flutter 112
141 ),
142 Column(
143 children: [
144 Text(
145 'First Row',
146 style: TextStyle(
147 fontSize: 20.0,
148 color: Colors.red,
149 ),
150 ),
151 SizedBox(
152 height: 10.0,
153 ),
154 Text(
155 'Second Row',
156 style: TextStyle(
157 fontSize: 20.0,
158 color: Colors.blue,
159 ),
160 ),
161 ],
162 ),
163 ],
164 ),
165 ),
166 ),
167 ],
168 );
169 }
170 }
Watch the bold sections. Reading them will explain how Consumer
widgets work. How do the Consumer widgets work?
The bold sections in the above code tells us one thing. In a
Consumer widget we must specify the type of the model that we
3. Inherited Widget, Provider and State Management in Flutter 113
want to access.
1 child: Consumer<CountingTheNumber>()
1 Consumer<CountingTheNumber>
1 void testMessage() {
2 message.startsWith('S')
3 ? message = 'Hi Sanjib'
4 : message = 'First letter is not S';
5 notifyListeners();
6 }
The third argument is child. If you plan to add large sub-tree under
the control of your Consumer widget, then just go ahead. Use that
child to build more complex UI. However, when state changes, the
child does not get affected.
The large sub-tree you’ve just added under Consumer doesn’t
change when the model changes.
If you run the code, the first screen shows you a name: “Sanjib
Sinha” at the bottom most Widget.
Next, we’ve pressed the button and it changes the state of the
bottom-most Widget, and gives us a message, such as “Hi Sanjib”.
While changing the State of the bottom-most Widget, it does not
change the top and bottom widgets.
So, that’s it. Although I feel we should learn provider using more
complex examples.
We’ll learn Provider by building more complex UI in the coming
chapters.
1 import 'package:flutter/material.dart';
2 import 'package:quiz_app/view/quiz_app.dart';
3
4 void main() {
5 runApp(QuizApp());
6 }
1 import 'package:flutter/material.dart';
2 import 'package:quiz_app/view/home_page.dart';
3
4 class QuizApp extends StatelessWidget {
5 // This widget is the root of your application.
6 @override
7 Widget build(BuildContext context) {
8 return MaterialApp(
9 title: 'Flutter Demo',
10 theme: ThemeData(
11 primarySwatch: Colors.blue,
12 ),
13 home: MyHomePage(title: 'Quiz App Home Page'),
14 );
15 }
16 }
1 import 'package:flutter/material.dart';
2 import 'package:quiz_app/controller/question_widget.dart';
3 import 'package:quiz_app/model/questions_list.dart';
4
5 class MyHomePage extends StatefulWidget {
6 MyHomePage({Key key, this.title}) : super(key: key);
7
8 final String title;
9
10 @override
11 _MyHomePageState createState() => _MyHomePageState();
12 }
13
14 class _MyHomePageState extends State<MyHomePage> {
15 int _counter = 0;
16
17 void _incrementCounter() {
18 setState(() {
19 _counter++;
20 });
21 if (_counter > 2) {
22 _counter = 0;
23 }
24 }
25
26 @override
27 Widget build(BuildContext context) {
28 var questions = questionList;
29 return Scaffold(
30 appBar: AppBar(
31 title: Text(widget.title),
32 ),
33 body: Center(
34 child: Column(
35 mainAxisAlignment: MainAxisAlignment.center,
3. Inherited Widget, Provider and State Management in Flutter 119
36 children: <Widget>[
37 QuestionWidget(questions: questions, counter:\
38 _counter),
39 ...(questions[_counter]['answers'] as List<St\
40 ring>)
41 .map(
42 (answer) => buildElevatedButton(answer),
43 )
44 .toList(),
45 ],
46 ),
47 ),
48 // This trailing comma makes auto-formatting nicer fo\
49 r build methods.
50 );
51 }
52
53 ElevatedButton buildElevatedButton(String answer) {
54 return ElevatedButton(
55 onPressed: _incrementCounter,
56 child: Text(
57 answer,
58 style: TextStyle(
59 fontSize: 30.0,
60 ),
61 ),
62 );
63 }
64 }
As you have noticed, we need two more files. One stays in model
directory. It consists of a List of questions and different answers.
Basically, it is a List of Map that consists of two data types, String
as key and Object or List as value.
3. Inherited Widget, Provider and State Management in Flutter 120
1 import 'package:flutter/material.dart';
2
3 class QuestionWidget extends StatelessWidget {
4 const QuestionWidget({
5 Key key,
6 @required this.questions,
7 @required int counter,
8 }) : _counter = counter,
9 super(key: key);
10
11 final List<Map<String, Object>> questions;
12 final int _counter;
13
14 @override
15 Widget build(BuildContext context) {
3. Inherited Widget, Provider and State Management in Flutter 121
16 return Text(
17 questions[_counter]['question'],
18 style: TextStyle(
19 fontSize: 25.0,
20 fontWeight: FontWeight.bold,
21 ),
22 );
23 }
24 }
1 // pubspec.yaml
2 # ...
3
4 dependencies:
5 flutter:
6 sdk: flutter
7
8 provider: ^4.0.0
1 ChangeNotifier
2 ChangeNotifierProvider
3 Consumer
1 void main() {
2 runApp(
3 ChangeNotifierProvider(
4 create: (context) => AnyModel(),
5 child: HomeApp(),
6 ),
7 );
8 }
1 void main() {
2 runApp(
3 MultiProvider(
4 providers: [
5 ChangeNotifierProvider(create: (context) => First\
6 Model()),
7 Provider(create: (context) => SecondClass()),
8 ],
9 child: HomeApp(),
10 ),
11 );
12 }
1 return Consumer<FirstModel>(
2 builder: (context, value, child) {
3 return Text("The value : ${value.firstModelVariable}"\
4 );
5 },
6 );
1 import 'package:flutter/widgets.dart';
2
3 /// using the mixin concept of dart that we have discussed
4 /// in our previous chapter
5 class CountingTheNumber with ChangeNotifier {
6 int value = 0;
7 void incrementTheValue() {
8 value++;
9 notifyListeners();
10 }
11
12 void decreaseValue() {
13 value--;
14 notifyListeners();
15 }
16 }
The above code snippets is quite simple. This is our model class
through which we want to manage the state of the counter in a
ChangeNotifier.
Next, we need to use the ChangeNotifierProvider in the right place.
Because we need to call two methods, using Consumer is wasteful.
We don’t want to change the whole UI with the help of our model
data.
That is why we will use another concept - ‘Provider.of’, instead of
using Consumer.
3. Inherited Widget, Provider and State Management in Flutter 128
1 import 'package:flutter/cupertino.dart';
2 import 'package:flutter/material.dart';
3 import 'package:provider/provider.dart';
4
5 import 'counter_class.dart';
6
7 class MyApp extends StatelessWidget {
8 // This widget is the root of your application.
9 @override
10 Widget build(BuildContext context) {
11 return MaterialApp(
12 title: 'Flutter Demo',
13 theme: ThemeData(
14 primarySwatch: Colors.blue,
15 visualDensity: VisualDensity.adaptivePlatformDens\
16 ity,
17 ),
18 home: ChangeNotifierProvider<CountingTheNumber>(
19 // it will not redraw the whole widget tree anymo\
20 re
21 create: (BuildContext context) => CountingTheNumb\
22 er(),
23 child: MyHomePage()),
24 );
25 }
26 }
27
28 class MyHomePage extends StatelessWidget {
29 /*
30 MyHomePage({Key key, this.title}) : super(key: key);
31
32 final String title;
33 */
34
35 @override
3. Inherited Widget, Provider and State Management in Flutter 129
71 RaisedButton(
72 onPressed: () => counter.decreaseValue(),
73 child: Text(
74 'Decrease',
75 style: TextStyle(
76 fontSize: 20.0,
77 ),
78 ),
79 ),
80 ],
81 ),
82 ),
83 // This trailing comma makes auto-formatting nicer fo\
84 r build methods.
85 );
86 }
87 }
Now, we can run the app and by tapping two buttons change the
value. Before that, let us have a close look at some parts of the above
code.
1 Text(
2 '${counter.value}',
3 style: Theme.of(context).textTheme.headline4,
4 ),
5 …
6 RaisedButton(
7 onPressed: () => counter.incrementTheValue(),
8 child: Text(
9 'Increase',
10 style: TextStyle(
11 fontSize: 20.0,
12 ),
13 ),
14 ),
15 …
16 RaisedButton(
17 onPressed: () => counter.decreaseValue(),
18 child: Text(
19 'Decrease',
20 style: TextStyle(
21 fontSize: 20.0,
22 ),
23 ),
24 ),
1 import 'package:flutter/material.dart';
2 import 'utilities/first_provider_example.dart';
3
4 void main() {
5 runApp(MyApp());
6 }
After that, we can run the app once again, and it turns the counter
value to 0. Now, we can test the decrease button (Figure 8.3).
3. Inherited Widget, Provider and State Management in Flutter 132
1 //main.dart
2
3 import 'models/providers/first_model_provider.dart';
4
5 import 'models/providers/counter_model_provider.dart';
6 import 'package:flutter/material.dart';
7 import 'package:provider/provider.dart';
8 import 'models/providers/second_model_provider.dart';
9 import 'views/my_app.dart';
10
11 void main() {
12 runApp(MultiProvider(
13 providers: [
14 ChangeNotifierProvider(
15 create: (context) => CountingTheNumber(),
16 ),
17 ChangeNotifierProvider(
18 create: (context) => FirstModelProvider(),
19 ),
3. Inherited Widget, Provider and State Management in Flutter 133
20 ],
21 child: MyApp(),
22 ));
23 }
24
25
26 // first_model_provider.dart
27
28 import 'package:flutter/widgets.dart';
29
30 class FirstModelProvider with ChangeNotifier {
31 String someDate = 'Some Date';
32
33 void supplyFirstData() {
34 someDate = 'Data Changed!';
35 print(someDate);
36 notifyListeners();
37 }
38
39 void clearData() {
40 someDate = 'Data Cleared!';
41 print(someDate);
42 notifyListeners();
43 }
44 }
45
46
47 // my_home_page.dart
48
49 import 'package:all_about_flutter_provider/models/provide\
50 rs/first_model_provider.dart';
51 import 'package:all_about_flutter_provider/models/provide\
52 rs/second_model_provider.dart';
53 import 'package:flutter/cupertino.dart';
54 import 'package:flutter/material.dart';
3. Inherited Widget, Provider and State Management in Flutter 134
55 import 'package:provider/provider.dart';
56
57 import '../models/providers/counter_model_provider.dart';
58
59 class MyHomePage extends StatelessWidget {
60 /*
61 MyHomePage({Key key, this.title}) : super(key: key);
62
63 final String title;
64 */
65 final String title = 'Using Provider Examples';
66
67 @override
68 Widget build(BuildContext context) {
69 /// MyHomePage is rebuilt when counter changes
70 final counter = Provider.of<CountingTheNumber>(contex\
71 t);
72
73 return Scaffold(
74 appBar: AppBar(
75 title: Text(title),
76 ),
77 body: SafeArea(
78 child: ListView(
79 padding: const EdgeInsets.all(10.0),
80 children: <Widget>[
81 Text(
82 'You have pushed the button this many times:',
83 style: TextStyle(fontSize: 25.0),
84 textAlign: TextAlign.center,
85 ),
86
87 /// consumer or selector
88 Text(
89 '${counter.value}',
3. Inherited Widget, Provider and State Management in Flutter 135
90 style: Theme.of(context).textTheme.headline4,
91 textAlign: TextAlign.center,
92 ),
93 SizedBox(
94 height: 10.0,
95 ),
96 Row(
97 mainAxisAlignment: MainAxisAlignment.spaceEve\
98 nly,
99 children: <Widget>[
100 RaisedButton(
101 onPressed: () => counter.increaseValue(),
102 child: Text(
103 'Increase',
104 style: TextStyle(
105 fontSize: 20.0,
106 ),
107 ),
108 ),
109 SizedBox(
110 height: 10.0,
111 ),
112 RaisedButton(
113 onPressed: () => counter.decreaseValue(),
114 child: Text(
115 'Decrease',
116 style: TextStyle(
117 fontSize: 20.0,
118 ),
119 ),
120 ),
121 ],
122 ),
123 SizedBox(
124 height: 10.0,
3. Inherited Widget, Provider and State Management in Flutter 136
125 ),
126 Column(
127 mainAxisAlignment: MainAxisAlignment.spaceEve\
128 nly,
129 children: <Widget>[
130 Container(
131 padding: const EdgeInsets.all(10.0),
132 color: Colors.red,
133 child: Consumer<FirstModelProvider>(
134 builder: (context, firstModelProvider\
135 , child) =>
136 RaisedButton(
137 child: Text(
138 'Press me!',
139 style: TextStyle(fontSize: 20.0),
140 ),
141 onPressed: () {
142 firstModelProvider.supplyFirstDat\
143 a();
144 },
145 ),
146 ),
147 ),
148 Container(
149 padding: const EdgeInsets.all(10.0),
150 color: Colors.white30,
151 child: Consumer<FirstModelProvider>(
152 builder: (context, firstModelProvider\
153 , child) => Text(
154 firstModelProvider.someDate,
155 style: TextStyle(fontSize: 40.0),
156 ),
157 ),
158 ),
159 SizedBox(
3. Inherited Widget, Provider and State Management in Flutter 137
In the above code, we have used two Providers, inside the main()
function.
3. Inherited Widget, Provider and State Management in Flutter 138
1 runApp(MultiProvider(
2 providers: [
3 ChangeNotifierProvider(
4 create: (context) => CountingTheNumber(),
5 ),
6 ChangeNotifierProvider(
7 create: (context) => FirstModelProvider(),
8 ),
9 ],
10 child: MyApp(),
11 ));
1 child: Consumer<FirstModelProvider>(
2 builder: (context, firstModelProvider\
3 , child) =>
4 RaisedButton(
5 child: Text(
6 'Press me!',
7 style: TextStyle(fontSize: 20.0),
8 ),
9 onPressed: () {
10 firstModelProvider.supplyFirstDat\
11 a();
12 },
13 ),
14 ),
1 // main.dart
2
3 import 'models/providers/first_model_provider.dart';
4
5 import 'models/providers/counter_model_provider.dart';
6 import 'package:flutter/material.dart';
7 import 'package:provider/provider.dart';
8 import 'models/providers/second_model_provider.dart';
9 import 'views/my_app.dart';
10
11 void main() {
12 runApp(MultiProvider(
13 providers: [
14 ChangeNotifierProvider(
15 create: (context) => CountingTheNumber(),
16 ),
17 ChangeNotifierProvider(
18 create: (context) => FirstModelProvider(),
19 ),
20 ChangeNotifierProvider(
21 create: (context) => SecondModelProvider(),
22 ),
23 ],
24 child: MyApp(),
25 ));
26 }
27
28
29 // second_model_provider.dart
30
31 import 'package:flutter/widgets.dart';
3. Inherited Widget, Provider and State Management in Flutter 140
32
33 class SecondModelProvider with ChangeNotifier {
34 String name = 'Some Name';
35 int age = 0;
36
37 void getFirstName() {
38 name = 'Json';
39 print(name);
40 notifyListeners();
41 }
42 }
43
44
45 // my_home_page.dart
46
47 import 'package:all_about_flutter_provider/models/provide\
48 rs/first_model_provider.dart';
49 import 'package:all_about_flutter_provider/models/provide\
50 rs/second_model_provider.dart';
51 import 'package:flutter/cupertino.dart';
52 import 'package:flutter/material.dart';
53 import 'package:provider/provider.dart';
54
55 import '../models/providers/counter_model_provider.dart';
56
57 class MyHomePage extends StatelessWidget {
58 /*
59 MyHomePage({Key key, this.title}) : super(key: key);
60
61 final String title;
62 */
63 final String title = 'Using Provider Examples';
64
65 @override
66 Widget build(BuildContext context) {
3. Inherited Widget, Provider and State Management in Flutter 141
172 firstModelProvider.clearData();
173 },
174 ),
175 ),
176 ),
177 SizedBox(
178 height: 10.0,
179 ),
180 Container(
181 padding: const EdgeInsets.all(10.0),
182 color: Colors.white30,
183 child: Consumer<SecondModelProvider>(
184 builder: (context, secondModel, child\
185 ) => Text(
186 secondModel.name,
187 style: TextStyle(fontSize: 40.0),
188 ),
189 ),
190 ),
191 SizedBox(
192 height: 10.0,
193 ),
194 Container(
195 padding: const EdgeInsets.all(10.0),
196 color: Colors.red[200],
197 child: Consumer<SecondModelProvider>(
198 builder: (context, secondModel, child\
199 ) => RaisedButton(
200 child: Text(
201 'Get First Name',
202 style: TextStyle(fontSize: 20.0),
203 ),
204 onPressed: () {
205 secondModel.getFirstName();
206 },
3. Inherited Widget, Provider and State Management in Flutter 145
207 ),
208 ),
209 ),
210 ],
211 ),
212 ],
213 ),
214 ),
215
216 /// This trailing comma makes auto-formatting nicer f\
217 or build methods.
218 );
219 }
220 }
This part of the code has handled the Consumer section. Therefore,
let us check that part first.
1 Container(
2 padding: const EdgeInsets.all(10.0),
3 color: Colors.white30,
4 child: Consumer<SecondModelProvider>(
5 builder: (context, secondModel, child\
6 ) => Text(
7 secondModel.name,
8 style: TextStyle(fontSize: 40.0),
9 ),
10 ),
11 ),
12 SizedBox(
13 height: 10.0,
14 ),
15 Container(
16 padding: const EdgeInsets.all(10.0),
17 color: Colors.red[200],
3. Inherited Widget, Provider and State Management in Flutter 146
18 child: Consumer<SecondModelProvider>(
19 builder: (context, secondModel, child\
20 ) => RaisedButton(
21 child: Text(
22 'Get First Name',
23 style: TextStyle(fontSize: 20.0),
24 ),
25 onPressed: () {
26 secondModel.getFirstName();
27 },
28 ),
29 ),
30 ),
1 // second_model_provider.dart
2
3 import 'package:flutter/widgets.dart';
4
5 class SecondModelProvider with ChangeNotifier {
6 String name = 'Some Name';
7 int age = 0;
8
9 void getFirstName() {
10 name = 'Json';
11 print(name);
12 notifyListeners();
13 }
14 }
Next, if you proceed, you will find how Provider and Consumer
work together. First, we have pressed the decrease button for 3
times. Next, we have pressed the ‘Press me’ button, and the ‘Data
3. Inherited Widget, Provider and State Management in Flutter 147
Changed’. After that, finally, we have pressed the ‘Get First Name’
button, and the name appears on the screen.
Each Consumer widget has persisted its state, one button-press does
not affect the other. The changed-data stays on the screen.
Before concluding this chapter, we will learn how we can separate
business logic, application logic and screen-view.
To do that, we will keep our models inside the ‘model’ folder and
keep our business logic there. We will keep our application logic
inside the ‘controller’ folder, and finally we get the screen-view
inside the ‘view’ folder.
As you can see, there are several options from which you can choose
the correct synonym of the word Mendacity.
Clicking any button will take you to the next question and, besides
it will also display the correct answer.
Therefore we need to tackle three changes at a single time! Al-
though these changes take place in different widgets, but not a
single widget is rebuilt.
The next image will show you how our app is working fine.
It certainly improves our fluttering performance because we’ve
used ChangeNotifier with Provider.
In the above image, it’s clearly visible that the next screenshot
shows us the next question and at the same time,it also displays
the synonym of the previous word - Mendacity.
1 import 'package:flutter/material.dart';
2 import 'package:provider/provider.dart';
3 import './remodelled-quiz-app/model/question_and_answer_m\
4 odel.dart';
5 import './remodelled-quiz-app/view/new_quiz_app.dart';
6
7 void main() {
8 runApp(
9 /// Providers are above [NewQuizApp] instead of inside it
10 MultiProvider(
11 providers: [
12 // ChangeNotifierProvider(create: (_) => Counter()),
13 // ChangeNotifierProvider(create: (_) => MyCounter()),
14 ChangeNotifierProvider(create: (_) => QuestionAndAnswerMo\
¹⁵https://github.com/sanjibsinha/quiz_app
¹⁶https://github.com/sanjibsinha/how_flutter_uses_dart/tree/main/lib/remodelled-quiz-
app
3. Inherited Widget, Provider and State Management in Flutter 151
15 del()),
16 ],
17 child: NewQuizApp(),
18 ),
19 );
20 }
1 import 'package:flutter/material.dart';
2 import './new_quiz_app_home.dart';
3
4 class NewQuizApp extends StatelessWidget {
5 const NewQuizApp({Key key}) : super(key: key);
6
7 @override
8 Widget build(BuildContext context) {
9 return MaterialApp(
10 home: NewQuizAppHome(),
11 );
12 }
13 }
What is ChangeNotifier?
According to the Flutter documentation:
One thing is clear. Our quiz app values, that means questions,
answers, etc can subscribe its changes.
1 import 'package:flutter/widgets.dart';
2
3 class QuestionAndAnswerModel extends ChangeNotifier {
4 List<Map<String, Object>> questions = [
5 {
6 'question': 'What is the synonym of Mendacity?',
7 'answers': ['truthfulness', 'daring', 'falsehood', 'enemy\
8 '],
9 },
10 {
11 'question': 'What is the synonym of Culpable?',
12 'answers': ['gay', 'guilty', 'falsehood', 'enemy'],
13 },
3. Inherited Widget, Provider and State Management in Flutter 153
14 {
15 'question': 'What is the synonym of Rapacious?',
16 'answers': ['guilty', 'daring', 'falsehood', 'greedy'],
17 },
18 ];
19 int counter = 0;
20
21 String answerChecking = 'Click to check correct answer!';
22
23 void incrementCounter() {
24 counter++;
25 notifyListeners();
26
27 if (counter > 2) {
28 counter = 0;
29 }
30 checkAnswer();
31 }
32
33 void checkAnswer() {
34 if (counter == 0) {
35 answerChecking = 'Synonym of Rapacious was Greedy.';
36 } else if (counter == 1) {
37 answerChecking = 'Synonym of Mendacity was Falsehood.';
38 } else if (counter == 2) {
39 answerChecking = 'Synonym of Culpable was Guilty.';
40 } else {
41 answerChecking = 'Click to check correct answer!';
42 }
43 }
44 }
1 import 'package:flutter/material.dart';
2 import 'package:provider/provider.dart';
3 import '../controller/question_widget.dart';
4 import '../controller/check_answer_widget.dart';
5 import '../controller/elevated_button_widget.dart';
6 import '../model/question_and_answer_model.dart';
7
8 class NewQuizAppHome extends StatelessWidget {
9 const NewQuizAppHome({Key key}) : super(key: key);
10
11 @override
12 Widget build(BuildContext context) {
13 return Container(
14 child: Scaffold(
15 appBar: AppBar(
16 title: Text('New Quiz App'),
17 ),
18 body: Center(
19 child: Padding(
20 padding: const EdgeInsets.all(8.0),
21 child: Column(
22 children: [
23 Text(
3. Inherited Widget, Provider and State Management in Flutter 155
59 );
60 }
61 }
1 import 'package:flutter/material.dart';
2 import 'package:provider/provider.dart';
3 import '../model/question_and_answer_model.dart';
4
5 class QuestionWidget extends StatelessWidget {
6 const QuestionWidget({
7 Key key,
8 @required this.questions,
9 @required this.counter,
10 }) : super(key: key);
11
12 final List<Map<String, Object>> questions;
13 final int counter;
14
15 @override
16 Widget build(BuildContext context) {
17 return Text(
18 context
19 .watch<QuestionAndAnswerModel>()
20 .questions[context.watch<QuestionAndAnswerModel>().counte\
21 r]
22 ['question'],
23 style: TextStyle(
24 fontSize: 25.0,
25 fontWeight: FontWeight.bold,
26 ),
27 );
28 }
29 }
1 context.watch<QuestionAndAnswerModel>().questions[context\
2 .watch<QuestionAndAnswerModel>().counter]['question'],
The same way, we can watch and check the correct answer.
1 import 'package:flutter/material.dart';
2 import 'package:provider/provider.dart';
3 import '../model/question_and_answer_model.dart';
4
5 class CheckAnswerWidget extends StatelessWidget {
6 const CheckAnswerWidget({
7 Key key,
8 }) : super(key: key);
9
10 @override
11 Widget build(BuildContext context) {
12 return Text(
13 context.watch<QuestionAndAnswerModel>().answerChecking,
14 style: TextStyle(
15 fontSize: 20.0,
16 ),
17 );
18 }
19 }
1 import 'package:flutter/material.dart';
2 import 'package:provider/provider.dart';
3 import '../model/question_and_answer_model.dart';
4
5 class ElevatedButtonWidget extends StatelessWidget {
6 ElevatedButtonWidget({Key key, this.answer}) : super(key:\
7 key);
8 final String answer;
9
10 @override
11 Widget build(BuildContext context) {
12 return ElevatedButton(
13 /// when you pass false to the listen parameter
14 /// like Provider.of<T>(context,listen: false) it will be\
15 have similar to read
16 ///
17 onPressed: () =>
18 // context.read<QuestionAndAnswerModel>().incrementCounte\
19 r(),
20 Provider.of<QuestionAndAnswerModel>(context, listen: fals\
21 e)
22 .incrementCounter(),
23 child: Text(
24 answer,
25 style: TextStyle(
26 fontSize: 30.0,
27 ),
28 ),
29 );
30 }
31 }
Once Flutter gets the provider, either it uses watch method to reflect
the changed property or read method to change the event or, in
other words, call the method on it.
Therefore how to use flutter provider doesn’t concern us. We want
the proof that provider works better and faster than stateful widget.
So let’s start with a simple stateful widget example where user
presses a button that increments the value. In this example we will
see how a stateful widget takes a toll on the whole widget tree.
1 import 'package:flutter/material.dart';
2
3 void main() {
4 runApp(MyApp());
5 }
6
7 class MyApp extends StatelessWidget {
8 // This widget is the root of your application.
9 @override
10 Widget build(BuildContext context) {
11 return MaterialApp(
12 title: 'Flutter Demo',
13 theme: ThemeData(
14 primarySwatch: Colors.blue,
15 ),
16 home: MyHomePage(title: 'Flutter Demo Home Page'),
17 );
18 }
19 }
20
21 class MyHomePage extends StatefulWidget {
22 MyHomePage({Key key, this.title}) : super(key: key);
23
24 final String title;
25
26 @override
27 _MyHomePageState createState() => _MyHomePageState();
28 }
29
3. Inherited Widget, Provider and State Management in Flutter 163
65 onPressed: _incrementCounter,
66 tooltip: 'Increment',
67 child: Icon(Icons.add),
68 ), // This trailing comma makes auto-formatting nicer for\
69 build methods.
70 );
71 }
72 }
Now we have pressed the counter button six times. This press of
button will rebuild the Floating action button widget, and even the
Icon widget. We cannot even avoid if we use provider package.
But why it should start rebuilding from the top widget?
We must demonstrate to establish the truth of our conjecture.
On the right side of Android Studio, we have opened the Flutter Per-
formance window and ticked the “Track Widget rebuilds”, which
shows us the proof.
the topmost MyHOmePage widget has been also hot reloaded or
restarted 6 times resulting in heavy memory consumption.
From MyHomePage to Scaffold to AppBar, everything has been
rebuilt six times. Can we have a close look at how widget rebuilds?
It clearly shows that MyHomePage has been rebuilt six time for a
singular button pressing. And that happens to Scaffold widget also.
However, when we will use provider package, this will never
happen. We’ll see the proof in a minute.
We can have more proof that will show you that in the similar way
Scaffold has been rebuilt six times too!
But we have enough proof. Now let’s concentrate on provider
package. How do we use provider efficiently so that we can avoid
the whole widget tree rebuilding?
Now, we come to the main point.
3. Inherited Widget, Provider and State Management in Flutter 165
First of all we will demonstrate how we can stop this bad effect on
our flutter app. If we use a stateful widget, a single button-press
rebuilds the whole widget tree and consumes a hell lot of memory.
We’ve just seen that effect.
Moreover, for a complex app structure using a stateful widget
seriously affects the speed.
Therefore, we must find the best solution. Our first target is simple.
We cannot stop the widget rebuilds totally. But we can try to make
it sure that less widgets are rebuilt in the process.
Well, what does that mean actually?
It means we can place one type of provider inside a one type of
widget and another type of widget inside another type of widget.
The golden rule is break your app in many small chunks. One model
class should have one task.
That will enhance the efficiency of our flutter app.
1 import 'package:flutter/material.dart';
2
3 class NumberModel extends ChangeNotifier {
4 int _counter = 0;
5 int get counter => _counter;
6 void incrementNumberByTwo() {
7 _counter = _counter + 2;
8 notifyListeners();
9 }
10 }
1 import 'package:flutter/material.dart';
2
3 class NameChangeModel extends ChangeNotifier {
4 String _name = 'Sanjib';
5 String get name => _name;
6 void changeName() {
7 _name = 'John';
8 notifyListeners();
9 }
10 }
1 import 'package:flutter/material.dart';
2
3 class NameClearModel extends ChangeNotifier {
4 String _name = ' ';
5 String get name => _name;
6 void clearName() {
7 _name = 'Sanjib';
8 notifyListeners();
9 }
10 }
Who will subscribe to these notifications? The First row widget will
listen to Number Model
According to our design patterns, the controller folder has three
related Widgets that will subscribe to these notifications.
Let us see one by one.
The FirstRowWidget has code like the following:
1 import 'package:flutter/material.dart';
2 import 'package:provider/provider.dart';
3 import '../model/number_model.dart';
4
5
6 class FirstRowWidget extends StatelessWidget {
7 const FirstRowWidget({
8 Key key,
9 }) : super(key: key);
10
11 @override
12 Widget build(BuildContext context) {
13 return Row(
14 mainAxisSize: MainAxisSize.min,
15 children: [
16 Expanded(
3. Inherited Widget, Provider and State Management in Flutter 168
17 child: Padding(
18 padding: EdgeInsets.all(20.0),
19 child: Column(
20 children: [
21 Text('You have pushed this button this time!'),
22
23 /// this is our one [NumberModel] listener
24 /// watch() will reflect the change in number
25 /// as one presses the button
26 ///
27 Text('${context.watch<NumberModel>().counter}'),
28 /**
29 * ElevatedButton(
30 /// this is our another [NumberModel] listener
31 /// read() will fire the event the changes the number
32 /// by adding 2
33 ///
34 onPressed: () =>
35 context.read<NumberModel>().incrementNumberByTwo(),
36 child: Text('Increment'),
37 ),
38 */
39 FloatingActionButton(
40 onPressed: () => context.read<NumberModel>().incrementNum\
41 berByTwo(),
42 tooltip: 'Increment',
43 child: Icon(Icons.add),
44 ), // Th
45 ],
46 ),
47 ),
48 ),
49 ],
50 );
51 }
3. Inherited Widget, Provider and State Management in Flutter 169
52 }
Now we get the changed look of flutter app with the help of
provider package.
On the left side we can see that we have pressed the button 4 times,
so we’ve got 8. And on the right hand side of the screen, we can see
how many widgets get rebuilt.
1 import 'package:flutter/material.dart';
2 import 'package:provider/provider.dart';
3 import '../model/name_change_model.dart';
4
5
6 class SecondRowWidget extends StatelessWidget {
7 const SecondRowWidget({
8 Key key,
9 }) : super(key: key);
10
11 @override
12 Widget build(BuildContext context) {
13 return Row(
14 mainAxisSize: MainAxisSize.min,
15 children: [
16 Expanded(
3. Inherited Widget, Provider and State Management in Flutter 170
17 child: Padding(
18 padding: EdgeInsets.all(20.0),
19 child: Column(
20 children: [
21 Text('${context.watch<NameChangeModel>().name}'),
22 /**
23 * ElevatedButton(
24 /// this is our another [NumberModel] listener
25 /// read() will fire the event the changes the number
26 /// by adding 2
27 ///
28 onPressed: () =>
29 context.read<NumberModel>().incrementNumberByTwo(),
30 child: Text('Increment'),
31 ),
32 */
33 FloatingActionButton(
34 onPressed: () => context.read<NameChangeModel>().changeNa\
35 me(),
36 tooltip: 'Increment',
37 child: Icon(Icons.add),
38 ), // Th
39 ],
40 ),
41 ),
42 ),
43 ],
44 );
45 }
46 }
This time, when we click the button to increment number, only First
row widget gets rebuilt. It is clearly visible in the image.
Next, we will see the code of third row widget.
1 import 'package:flutter/material.dart';
2 import 'package:provider/provider.dart';
3 import '../model/name_clear_model.dart';
4
5
6 class ThirdRowWidget extends StatelessWidget {
7 const ThirdRowWidget({Key key}) : super(key: key);
8
9 @override
10 Widget build(BuildContext context) {
11 return Row(
12 mainAxisSize: MainAxisSize.min,
13 children: [
14 Expanded(
15 child: Padding(
16 padding: EdgeInsets.all(20.0),
17 child: Column(
18 children: [
19 Text('${context.watch<NameClearModel>().name}'),
20 /**
3. Inherited Widget, Provider and State Management in Flutter 172
21 * ElevatedButton(
22 /// this is our another [NumberModel] listener
23 /// read() will fire the event the changes the number
24 /// by adding 2
25 ///
26 onPressed: () =>
27 context.read<NumberModel>().incrementNumberByTwo(),
28 child: Text('Increment'),
29 ),
30 */
31 FloatingActionButton(
32 onPressed: () => context.read<NameClearModel>().clearName\
33 (),
34 tooltip: 'Increment',
35 child: Icon(Icons.add),
36 ), // Th
37 ],
38 ),
39 ),
40 ),
41 ],
42 );
43 }
44 }
If you press the third button, the third row widget will listen to the
event declared in clear name model.
Let’s see the last image where we will see that every button-press
restricts the unnecessary widget rebuilds. Scaffold, AppBar widgets
have never been rebuilt, although it happened in the case of stateful
widget.
On the right hand side, we can clearly see that Scaffold and AppBar
widgets have never been rebuilt whenever we’ve pressed the button.
How many times we have pressed, that really doesn’t matter. The
widget rebuilds are restricted in great amount.
3. Inherited Widget, Provider and State Management in Flutter 173
1 import 'package:flutter/material.dart';
2 import 'package:provider/provider.dart';
3 import 'model/number_model.dart';
4 import 'model/name_change_model.dart';
5 import 'model/name_clear_model.dart';
6 import 'view/french_test.dart';
7
8 void main() {
9 runApp(
10 /// Providers are above [FrenchTestApp] instead of inside\
11 it
12 MultiProvider(
13 providers: [
14 ChangeNotifierProvider(create: (_) => NumberModel()),
15 ChangeNotifierProvider(create: (_) => NameChangeModel()),
16 ChangeNotifierProvider(create: (_) => NameClearModel()),
17 ],
18 child: FrenchTestApp(),
19 ),
20 );
21 }
1 import 'package:flutter/material.dart';
2 import 'french_test_home.dart';
3
4 class FrenchTestApp extends StatelessWidget {
5 // This widget is the root of your application.
6 @override
7 Widget build(BuildContext context) {
8 return MaterialApp(
9 title: 'Flutter Demo',
10 theme: ThemeData(
11 // This is the theme of your application.
12 primarySwatch: Colors.blue,
13 ),
14 home: FrenchTestHome(),
15 );
16 }
17 }
In the view folder we’ve kept two more stateless widgets which will
actually render the three controller widgets.
3. Inherited Widget, Provider and State Management in Flutter 175
1 import 'package:flutter/material.dart';
2
3 import 'french_test_first_view.dart';
4
5 class FrenchTestHome extends StatelessWidget {
6 const FrenchTestHome({Key key}) : super(key: key);
7
8 @override
9 Widget build(BuildContext context) {
10 return Scaffold(
11 appBar: AppBar(
12 title: Text('French Test'),
13 ),
14 body: FrenchTestFirstView(),
15 );
16 }
17 }
Since we have one page or screen, we’ll identify the widget by name
FrenchTestFirstView. And the code is like the following.
1 import 'package:flutter/material.dart';
2 import '../controller/first_row_widget.dart';
3 import '../controller/second_row_widget.dart';
4 import '../controller/third_row_widget.dart';
5
6 class FrenchTestFirstView extends StatelessWidget {
7 const FrenchTestFirstView({Key key}) : super(key: key);
8
9 @override
10 Widget build(BuildContext context) {
11 return Center(
12 child: Column(
13 children: [
14 FirstRowWidget(),
3. Inherited Widget, Provider and State Management in Flutter 176
15 SecondRowWidget(),
16 ThirdRowWidget(),
17 ],
18 ),
19 );
20 }
21 }
We’ve successfully split the long widget tree into smaller reusable
widgets.
And our code is functioning perfectly reducing the widget rebuilds
process.
For more Flutter related Articles and Resources¹⁸
1 Provider
2
3 StateProvider
4
5 StateNotifierProvider
6
7 ChangeNotifierProvider
8
9 StreamProvider
10
11 FutureProvider
12
13 ScopedProvider
our need. That means, one option is always the best for one specific
problem.
We’ll come to that point later. In this chapter. Because it needs a
very detailed introspection.
As an example, when we want to watch or fetch a data from
our model class we can use the simplest Provider from Riverpod
package.
Now, we are going to learn how to use the simplest Provider from
the Riverpod package.
Just to make this chapter more interesting we will use our old
friend, the Provider state management package, also. In fact, you
can always use two packages side by side.
1 dependencies:
2 flutter:
3 sdk: flutter
4 flutter_riverpod:
5 provider:
We’ve not mentioned the version so that the packages can depend
on the latest one available.
Just like our previous Provider chapter, we have used a model class
for this Riverpod starter.
1 import 'package:flutter/widgets.dart';
2 import 'package:flutter_riverpod/flutter_riverpod.dart';
3
4 class ProviderModel extends ChangeNotifier {
5 String _littleMonk = 'I am any String data!';
6 String get littleMonk => _littleMonk;
7
8 String fetchName(String str) {
9 _littleMonk = str;
10 return _littleMonk;
11 }
12
13 void changeName() {
14 _littleMonk = 'Now I am Little Monk';
15 notifyListeners();
16 }
17 }
18
19 final classTypeProviderModel = Provider<ProviderModel>((r\
20 ef) {
21 return ProviderModel();
22 });
1 import 'package:flutter/material.dart';
2 import 'package:flutter_riverpod/flutter_riverpod.dart';
3 import 'provider/controller/provider_example_widget.dart';
4
5 void main() {
6 runApp(ProviderScope(child: App()));
7 }
8
9 class App extends StatelessWidget {
10 // This widget is the root of your application.
11 @override
12 Widget build(BuildContext context) {
13 return MaterialApp(
3. Inherited Widget, Provider and State Management in Flutter 182
1 void main() {
2 runApp(ProviderScope(child: App()));
3 }
It means the Riverpod package uses just one, yes, a single Inherit-
edWidget. And we should place it above the whole widget tree.
I’m not going to detail how it can store state of all Provider objects.
But we can use them anywhere.
3. Inherited Widget, Provider and State Management in Flutter 183
1 import 'package:flutter/material.dart';
2 import 'package:flutter_riverpod/flutter_riverpod.dart';
3 //import 'package:provider/provider.dart';
4 import '../model/any_type_provider_model.dart';
5
6 class ProviderExampleWidget extends StatelessWidget {
7 const ProviderExampleWidget({Key key}) : super(key: key);
8
9 @override
10 Widget build(BuildContext context) {
11 return Column(
12 children: [
13 SizedBox(
14 height: 10.0,
15 ),
16 Padding(
17 padding: const EdgeInsets.all(18.0),
18 child: Text(
19 'Riverpod Provider example where we watch a String member\
20 variable'
21 ' that we have passed through a class method.',
22 style: TextStyle(
23 fontSize: 20.0,
24 fontWeight: FontWeight.bold,
25 ),
26 ),
27 ),
28 Padding(
3. Inherited Widget, Provider and State Management in Flutter 184
1 Consumer(
2 builder: (context, watch, child) {
3 final x = watch(classTypeProviderModel);
4 return Text(
5 x.fetchName('We can now pass any string data...'),
6 style: TextStyle(
7 fontSize: 50.0,
8 ),
9 );
10 },
11 ),
3. Inherited Widget, Provider and State Management in Flutter 185
1 import 'package:flutter/material.dart';
2 import 'package:flutter_riverpod/flutter_riverpod.dart';
3 import 'elevated_button_widget.dart';
4 import '../model/any_type_provider_model.dart';
5
6 class ProviderExampleWidget extends ConsumerWidget {
7 const ProviderExampleWidget({Key key}) : super(key: key);
8
9 @override
¹⁹https://github.com/sanjibsinha/riverpod_examples
3. Inherited Widget, Provider and State Management in Flutter 186
45 littleMonk
46 .fetchName('Now we can pass any data to change above data\
47 ,'
48 ' and watch it!'),
49 style: TextStyle(fontSize: 30.0),
50 ),
51 ),
52 ),
53 SizedBox(
54 height: 10.0,
55 ),
56 ElevatedButtonWidget(),
57 ],
58 );
59 }
60 }
1 Text(
2 littleMonk.littleMonk,
3 style: TextStyle(fontSize: 30.0),
4 ),
We can also change the state by passing a string data like this:
3. Inherited Widget, Provider and State Management in Flutter 188
1 Text(
2 littleMonk
3 .fetchName('Now we can pass any data to change above data\
4 ,'
5 ' and watch it!'),
6 style: TextStyle(fontSize: 30.0),
7 ),
1 ElevatedButtonWidget(),
Let us see how we can now use the old Provider package to change
the provided object state by pressing a button.
1 import 'package:flutter/material.dart';
2 import 'package:provider/provider.dart';
3 import '../model/any_type_provider_model.dart';
4
5 class ElevatedButtonWidget extends StatelessWidget {
6 const ElevatedButtonWidget({
7 Key key,
8 }) : super(key: key);
9
10 @override
11 Widget build(BuildContext context) {
12
13 return ChangeNotifierProvider<ProviderModel>(
14 // <--- ChangeNotifierProvider
15 create: (context) => ProviderModel(),
16 child: Column(
17 children: [
18 Container(
19 padding: const EdgeInsets.all(15),
20 color: Colors.blue[200],
21 child: Consumer<ProviderModel>(
22 // <--- Consumer
23 builder: (context, myModel, child) {
24 return FloatingActionButton(
25 onPressed: () => myModel.changeName(),
26 child: Icon(Icons.add),
27 tooltip: 'Change Name',
28 );
29 },
30 ),
31 ),
32 SizedBox(
33 height: 20.0,
34 ),
35 Container(
3. Inherited Widget, Provider and State Management in Flutter 190
Riverpod Provider and the old Provider package reduce the widget
rebuilds
As we have pressed the button the It changes the state of the
provided object globally. However, as you can see the locally
scoped data state has not been affected at all.
As we’ve tracked the widget rebuilds we can also see that on the
right hand side the statistics show how it benefits our app by
reducing widget rebuilds.
When we’ve clicked the button, it only affects our custom Elevat-
edButtonWidget() widget.
For more Flutter related Articles and Resources²⁰
²⁰https://sanjibsinha.com
4. Theme, Styling, Fonts
and Images Best
Practices in Flutter
Application wise global theme always reduces the workload. More-
over, from a central point we can control how our flutter app should
look like.
Although by default flutter comes with some help, still we need to
know how to use them wisely.
Since we’re talking about theme, we should know one thing first.
Theme is an inherited widget. And for that reason we can access
Theme from anywhere in our app.
We have seen the same thing in Provider package. Instead of
manually writing inherited widget, we use Provider package in
flutter.
Because provider is a wrapper around inherited widget, it manages
state easily.
The same thing happens when we use theme.
We actually take advantage of inherited widget.
For more Flutter related Articles and Resources²¹
Although in this article we’ll take a look at color only, yet that
would be extensive. Color defines many things in a flutter app.
Moreover, style depends on color. So an attempt to impose applica-
tion wide uniformity is necessary.
Let’s take a look at the flutter app, we’re going to build.
This app will let user choose the correct answer to find the synonym
of a word.
As a result, we have a question and four elevated buttons on the
screen. Now user can click any button to check whether that answer
is correct or not.
1 dependencies:
2 flutter:
3 sdk: flutter
4
5 provider: ^5.0.0
Now, we’ll see how we can impose the uniformity of color red
across the whole application.
How do you get the theme color in flutter?
We want various shades of color red across the whole app. Not only
one shade of red, but you’ll find that we can use various shades of
red. From dark to light, using global theme.
To start with we have a main method that runs the app:
1 void main() {
2 runApp(
3 /// Providers are above [StylingThemingApp] instead of in\
4 side it,
5 /// so that tests can use [StylingThemingApp] while mocki\
6 ng the providers
7 MultiProvider(
8 providers: [
9 ChangeNotifierProvider(create: (_) => QuestionAndAnswerMo\
10 del()),
11 ],
4. Theme, Styling, Fonts and Images Best Practices in Flutter 194
12 child: StylingThemingApp(),
13 ),
14 );
15 }
1 return MaterialApp(
2 title: 'Styling Theming App',
3 theme: ThemeData(
4 primarySwatch: Colors.red,
5 ),
6 home: StylingThemingFirstView(),
7 );
1 primarySwatch: Colors.red,
1 import 'package:flutter/widgets.dart';
2
3 class QuestionAndAnswerModel extends ChangeNotifier {
4 List<Map<String, Object>> questions = [
5 {
6 'question': 'What is the synonym of Mendacity?',
7 'answers': ['truthfulness', 'daring', 'falsehood', 'enemy\
8 '],
9 },
10 {
11 'question': 'What is the synonym of Culpable?',
12 'answers': ['gay', 'guilty', 'falsehood', 'enemy'],
13 },
14 {
15 'question': 'What is the synonym of Rapacious?',
16 'answers': ['guilty', 'daring', 'falsehood', 'greedy'],
17 },
18 ];
19 int counter = 0;
20
21 String answerChecking = 'Click the accurate button for co\
22 rrect answer!';
23
24 void incrementCounter() {
25 counter++;
26 notifyListeners();
27
28 if (counter > 2) {
29 counter = 0;
30 }
31 checkAnswer();
32 }
33
34 void checkAnswer() {
35 if (counter == 0) {
4. Theme, Styling, Fonts and Images Best Practices in Flutter 197
Now there are two ways, we can work with this model class and
methods. We can use the List map() method or in a Listview.builder
we can directly work with the list.
Here, we’ve used List map() method in our main screen.
54 }
55 }
17 r]
18 ['question'],
19 style: TextStyle(
20 fontSize: 25.0,
21 fontWeight: FontWeight.bold,
22 ),
23 );
24 }
25 }
As you see, we’ve not used our global color theme inside this Text
style. We’ll do that later.
The other controller widget that checks whether the answers are
correct or not, doesn’t also uses the global color theme.
27 }
28 }
1 color: Theme.of(context).primaryColorLight,
Since we’ve chosen the the light shade of red, the text inside
elevated buttons is of light shade.
Now we can click any button to check our answer, and the unifor-
mity will be maintained.
We’ll concentrate on two key points. Firstly, watch this line of code:
1 color: Theme.of(context).primaryColorDark,
1 padding: EdgeInsets.all(8),
First of all, we have set the border to primary color dark. That
means each button will have a dark red border of certain width.
At the same time, each button will maintain a padding. It ensures
that there will be a definite distance between each button and the
border.
We can not only set the background and foreground color, but also
maintain the consistency in our color scheme from one single place.
How about changing the color from red to green?
Let’s try.
4. Theme, Styling, Fonts and Images Best Practices in Flutter 205
Figure 4.3 – How do you change the theme color globally in flutter
1 return MaterialApp(
2 title: 'Styling Theming App',
3 theme: ThemeData(
4 primarySwatch: Colors.lightGreen,
5 ),
6 home: StylingThemingFirstView(),
7 );
1 fonts:
2 - family: Anton
3 fonts:
4 - asset: assets/fonts/Anton-Regular.ttf
5
6 - family: LibreBaskerville
7 fonts:
8 - asset: assets/fonts/LibreBaskerville-Regular.ttf
9 - asset: assets/fonts/LibreBaskerville-Bold.ttf
10 weight: 700
11
12 - family: SyneMono
13 fonts:
14 - asset: assets/fonts/SyneMono-Regular.ttf
15
16 - family: TrainOne
17 fonts:
18 - asset: assets/fonts/TrainOne-Regular.ttf
1 import 'package:flutter/material.dart';
2 import 'views/lbta_material_scaffold.dart';
3
4 void main() {
5 runApp(ListviewBuilderThemingApp());
6 }
Next, we need the Material App design part where we can set the
theme.
26 appBarTheme: AppBarTheme(
27 textTheme: ThemeData.light().textTheme.copyWith(
28 headline6: TextStyle(
29 fontFamily: 'Anton',
30 fontSize: 22.0,
31 // fontWeight: FontWeight.bold,
32 ),
33 ),
34 ),
35 ),
36 home: ScaffoldingLBTA(),
37 );
38 }
39 }
We want that global title theme should differ with the AppBar title
theme.
We’ve done that in the above code.
We’ve defined the Material App part, but we need to Scaffold the
body part.
Then we need the first view where we can declare other views. And
in those views, we can separately define our custom fonts.
Based on the fonts family keys we’ve already set in the pub-
spec.yaml file, we can move ahead.
4. Theme, Styling, Fonts and Images Best Practices in Flutter 211
As you see in the above code, there are four views listed as children.
We’ll come to the GoogleFontView() part later. Because we have
used Google font package there, it has no relation with the other
three custom view page.
However, we’ve not finished yet. Now we will create two more
views based on the font family we’ve had declared in our pub-
spec.yaml file.
In those views, we’ll set the text style explicitly.
1 /* style: TextStyle(
2 fontFamily: 'LibreBaskerville',
3 fontSize: 20,
4 color: Theme.of(context).primaryColorDark,
5 ), */
6 style: Theme.of(context).textTheme.headline6,
1 color: Theme.of(context).textTheme.headline6.color.withRe\
2 d(255),
We’ve also set the color of the container in the same way.
4. Theme, Styling, Fonts and Images Best Practices in Flutter 216
1 Container(
2 margin: EdgeInsets.all(8),
3 padding: EdgeInsets.all(8),
4 color: Theme.of(context).textTheme.headline6.color.withGr\
5 een(250),
6 child: Text(
7
8 ...
We’ve not finished our app. Yet, up to this point it looks like this:
Figure 4.4 – Adding dependencies of Google fonts and display them through
Text widgets in different containers
1 dependencies:
2 google_fonts: ^2.0.0
24 color: Colors.amberAccent,
25 child: Text(
26 'The second instance of using Google font "opensans" in b\
27 old',
28 style: GoogleFonts.openSans(
29 fontWeight: FontWeight.bold,
30 fontSize: 20.0,
31 ),
32 ),
33 ),
34 ],
35 ),
36 );
37 }
38 }
1 import 'package:google_fonts/google_fonts.dart';
Figure 4.5 – Adding google_fonts package for Flutter and display them on app’s
screen
1 style: GoogleFonts.openSans(
2 fontWeight: FontWeight.bold,
3 fontSize: 20.0,
4 ),
The following code snippet will show you, how we did that.
1 child: Image.asset(
2 'assets/images/s.png',
3 width: 150,
4 height: 150,
5 fit: BoxFit.fitHeight,
6 ),
1 flutter:
2 uses-material-design: true
3 assets:
4 - assets/images/
15 'https://i0.wp.com/sanjibsinha.com/wp-content/uploads/202\
16 1/04/a-1.jpg'),
17 );
18 }
19 }
We can scroll the app, and the second image that comes from API
is quite prominent.
1 Column(
2 children: [
3 RaisedButton(
4 color: Colors.redAccent,
5 textColor: Colors.white,
6 onPressed: () {
7 print('Pressed Raised Button');
8 },
9 child: Text('Press RaisedButton',
10 style: Theme.of(context).textTheme.headline4,
11 ),
12 ),
13 SizedBox(height: 25,),
14 FlatButton(
15 color: Colors.lightGreenAccent,
16 textColor: Colors.white,
17 onPressed: () {
18 print('Pressed FlatButton');
19 },
20 child: Text('Press FlatButton',
21 style: Theme.of(context).textTheme.headline4,
22 ),
4. Theme, Styling, Fonts and Images Best Practices in Flutter 227
23 ),
24 SizedBox(height: 25,),
25 OutlineButton(
26 borderSide: BorderSide(color: Colors.black,),
27 textColor: Colors.redAccent,
28 onPressed: () {
29 print('Pressed OutlineButton');
30 },
31 child: Text('Press OutlineButton',
32 style: Theme.of(context).textTheme.headline4,
33 ),
34 ),
35 ],
36 ),
On the right hand side of our screen, we can see another interesting
development. We can press any button several times, but that does
not increase widget rebuilds.
Why does it happen?
If we watch the inheritance tree of RaisedButton, it makes clear.
The inheritance tree looks like this:
1 OBJECT->DIAGNOSTICABLETREE->WIDGET->STATELESSWIDGET->MATE\
2 RIALBUTTON->RAISEDBUTTON.
4. Theme, Styling, Fonts and Images Best Practices in Flutter 228
1 Column(
2 children: [
3 ElevatedButton(
4 style: ButtonStyle(
5 backgroundColor: MaterialStateProperty.all(Colors.redAcce\
6 nt),
7 foregroundColor: MaterialStateProperty.all(Colors.white),
8 ),
9 onPressed: () {
10 print('Pressed Elevated Button');
11 },
12 child: Text('Press ElevatedButton',
13 style: TextStyle(
14 fontFamily: 'Anton',
15 fontSize: 25,
16 ),
4. Theme, Styling, Fonts and Images Best Practices in Flutter 229
17 ),
18 ),
19 SizedBox(height: 25,),
20 TextButton(
21 style: ButtonStyle(
22 backgroundColor: MaterialStateProperty.all(Colors.blue),
23 foregroundColor: MaterialStateProperty.all(Colors.white),
24 ),
25 onPressed: () {
26 print('Pressed FlatButton');
27 },
28 child: Text('Press FlatButton',
29 style: TextStyle(
30 fontFamily: 'LibreBaskerville',
31 fontSize: 25,
32 ),
33 ),
34 ),
35 SizedBox(height: 25,),
36 OutlinedButton(
37 style: ButtonStyle(
38 backgroundColor: MaterialStateProperty.all(Colors.green),
39 foregroundColor: MaterialStateProperty.all(Colors.white),
40 ),
41 onPressed: () {
42 print('Pressed OutlineButton');
43 },
44 child: Text('Press OutlineButton',
45 style: TextStyle(
46 fontFamily: 'Anton',
47 fontSize: 25,
48 ),
49 ),
50 ),
51 ],
4. Theme, Styling, Fonts and Images Best Practices in Flutter 230
52 ),
As you can see, this style part is same for all three new buttons that
replace the new ones.
1 style: ButtonStyle(
2 backgroundColor: MaterialStateProperty.all(Colors.green),
3 foregroundColor: MaterialStateProperty.all(Colors.white),
4 ),
On the right hand side of the screen, we have tracked the number
of widget rebuilds. Now it is much higher.
Why does it happen?
The reason is simple, if you take a look at the inheritance tree of
ElevatedButton widget, it makes clear.
4. Theme, Styling, Fonts and Images Best Practices in Flutter 231
1 OBJECT->DIAGNOSTICABLETREE->WIDGET->STATEFULWIDGET->BUTTO\
2 NSTYLEBUTTON->ELEVATEDBUTTON
1 Column(
2 children: [
3 ElevatedButton(
4 style: ElevatedButton.styleFrom(
5 primary: Colors.blueGrey,
6 onPrimary: Colors.white,
7 ),
8 onPressed: () {
9 print('Pressed Elevated Button');
10 },
11 child: Text('Press ElevatedButton',
12 style: TextStyle(
13 fontFamily: 'Anton',
14 fontSize: 25,
15 ),
16 ),
17 ),
18 SizedBox(height: 25,),
4. Theme, Styling, Fonts and Images Best Practices in Flutter 232
19 TextButton(
20 style: TextButton.styleFrom(
21 elevation: 40.0,
22 backgroundColor: Colors.yellow,
23 ),
24 onPressed: () {
25 print('Pressed FlatButton');
26 },
27 child: Text('Press FlatButton',
28 style: TextStyle(
29 fontFamily: 'LibreBaskerville',
30 fontSize: 25,
31 ),
32 ),
33 ),
34 SizedBox(height: 25,),
35 OutlinedButton(
36 style: OutlinedButton.styleFrom(
37 primary: Colors.white,
38 backgroundColor: Colors.red,
39 side: BorderSide(
40 color: const Color(4278190000),
41 ),
42 elevation: 40.0,
43 ),
44 onPressed: () {
45 print('Pressed OutlinedButton');
46 },
47 child: Text('Press OutlineButton',
48 style: TextStyle(
49 fontFamily: 'Anton',
50 fontSize: 25,
51 ),
52 ),
53 ),
4. Theme, Styling, Fonts and Images Best Practices in Flutter 233
54 ],
55 ),
Now, the main difference is in the style section. Now each class
takes the style from within. Take a look at the OutlinedButton style:
1 style: OutlinedButton.styleFrom(
2 primary: Colors.white,
3 backgroundColor: Colors.red,
4 side: BorderSide(
5 color: const Color(4278190000),
6 ),
7 elevation: 40.0,
8 ),
On the left side we can see the emulator. And on the right side
the DevTools shows the app structure. Since we’ve clicked the
OutLinedButton, it gives us all the details about the button.
Moreover, it inspects the whole UI layout and gives us details of the
state of flutter app.
Figure 4.11 – DevTools inspects the whole UI layout and gives us details
4. Theme, Styling, Fonts and Images Best Practices in Flutter 236
DevTools is the answer. In addition, the next image will show you
three different types of bars. Moreover, these bars in the DevTools
will show you how the app performs.
The Jank performance issues come with slow frame that makes your
app slower.
1 import 'package:flutter/foundation.dart';
2
3 class ExpenseList {
4 String id;
5 String title;
6 double amount;
7 DateTime date;
8
9 ExpenseList({
10 @required this.id,
11 @required this.title,
12 @required this.amount,
13 @required this.date,
14 });
15 }
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 238
As you can see, we’ve added two expense list using the class.
Of course, flutter allows us to do that.
²³https://sanjibsinha.com
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 239
1 @override
2 Widget build(BuildContext context) {
3 return Center(
4 child: ListView(
5 padding: EdgeInsets.all(8),
6 children: [
7 Container(
8 width: double.infinity,
9 margin: EdgeInsets.all(8),
10 padding: EdgeInsets.all(8),
11 color: Theme.of(context).accentColor,
12 child: Card(
13 child: Text(
14 'Chart',
15 style: Theme.of(context).textTheme.headline5,
16 ),
17 elevation: 10,
18 ),
19 ),
20 Column(
21 crossAxisAlignment: CrossAxisAlignment.center,
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 242
22 children: expenseList.map((expense) {
23 return Row(
24 mainAxisAlignment: MainAxisAlignment.end,
25 children: [
26 Icon(Icons.insert_emoticon_rounded),
27 Card(
28 child: Text(
29 'Item: ${expense.title}',
30 style: Theme.of(context).textTheme.headline5,
31 ),
32 elevation: 10,
33 ),
34 Card(
35 child: Text(
36 'Expense: ${expense.amount.toString()}',
37 style: Theme.of(context).textTheme.headline6,
38 ),
39 elevation: 10,
40 ),
41 ],
42 );
43 }).toList(),
44 ),
45 ],
46 ),
47 );
48 }
Now, we can change the design the list a little bit. So it looks like
this.
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 243
1 @override
2 Widget build(BuildContext context) {
3 return Center(
4 child: ListView(
5 padding: EdgeInsets.all(8),
6 children: [
7 Container(
8 width: double.infinity,
9 margin: EdgeInsets.all(8),
10 padding: EdgeInsets.all(8),
11 color: Theme.of(context).accentColor,
12 child: Card(
13 child: Text(
14 'Chart',
15 style: Theme.of(context).textTheme.headline5,
16 ),
17 elevation: 10,
18 ),
19 ),
20 Column(
21 crossAxisAlignment: CrossAxisAlignment.center,
22 children: expenseList.map((expense) {
23 return Container(
24 margin: EdgeInsets.all(5),
25 padding: EdgeInsets.all(5),
26 child: Row(
27 mainAxisAlignment: MainAxisAlignment.end,
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 244
28 children: [
29 Icon(Icons.insert_emoticon_rounded),
30 Column(
31 children: [
32 Card(
33 child: Text(
34 'Item: ${expense.title}',
35 style: Theme.of(context).textTheme.headline5,
36 ),
37 elevation: 10,
38 ),
39 SizedBox(
40 height: 10,
41 ),
42 Card(
43 child: Text('${expense.date.toString()}',
44 style: TextStyle(
45 fontSize: 15.0,
46 fontWeight: FontWeight.bold,
47 )),
48 elevation: 10,
49 ),
50 ],
51 ),
52 Card(
53 child: Text(
54 '${expense.amount.toString()}',
55 style: Theme.of(context).textTheme.headline6,
56 ),
57 elevation: 10,
58 ),
59 ],
60 ),
61 );
62 }).toList(),
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 245
63 ),
64 ],
65 ),
66 );
67 }
1 @override
2 Widget build(BuildContext context) {
3 return Center(
4 child: ListView(
5 padding: EdgeInsets.all(8),
6 children: [
7 Container(
8 width: double.infinity,
9 margin: EdgeInsets.all(8),
10 padding: EdgeInsets.all(8),
11 color: Theme.of(context).accentColor,
12 child: Card(
13 child: Text(
14 'Chart',
15 style: Theme.of(context).textTheme.headline5,
16 ),
17 elevation: 10,
18 ),
19 ),
20 Column(
21 crossAxisAlignment: CrossAxisAlignment.center,
22 children: expenseList.map((expense) {
23 return Container(
24 margin: EdgeInsets.all(5),
25 padding: EdgeInsets.all(5),
26 child: Row(
27 mainAxisAlignment: MainAxisAlignment.end,
28 children: [
29 Icon(Icons.insert_emoticon_rounded),
30 Column(
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 248
31 children: [
32 Card(
33 child: Text(
34 '${expense.title}',
35 style: Theme.of(context).textTheme.headline5,
36 ),
37 elevation: 10,
38 ),
39 SizedBox(
40 height: 10,
41 ),
42 Card(
43 child: Text('${expense.date.toString()}',
44 style: TextStyle(
45 fontSize: 12.0,
46 fontWeight: FontWeight.bold,
47 )),
48 elevation: 10,
49 ),
50 ],
51 ),
52 Card(
53 child: Text(
54 '${expense.amount.toString()}',
55 style: Theme.of(context).textTheme.headline6,
56 ),
57 elevation: 10,
58 ),
59 Expanded(
60 child: Text(
61 ' -> DELETE',
62 softWrap: false,
63 overflow: TextOverflow.fade,
64 style: Theme.of(context).textTheme.headline6,
65 ),
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 249
66 )
67 ],
68 ),
69 );
70 }).toList(),
71 ),
72 ],
73 ),
74 );
75 }
We’ll build the full app. But we cannot do that in one single post.
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 250
Figure 5.3 – Finally it looks this, although not very good looking! So, next we’ll
try to improve the design
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 251
As you can see, this time our expense list app looks far better than
before. However it’s not finished yet. It’s just the beginning.
What is ListView in Flutter?
Theoretically the ListView is a widget that allows user to scroll.
However, it has many other purposes.
It displays its children widget in a scrolling manner. As a result, we
can accommodate many other widgets inside it.
By the way, some widgets are invisible. But, they allow the visible
widgets to show the designs.
ListView belongs to the invisible side. Yet it’s one of the rarest
invisible widget, that scrolls.
As we progress, let’s try to understand the app design.
We’re going to make a flutter app that will add expenses in a list.
Moreover, it’ll have a delete button, so we can delete any of the
item from the list.
Before going to the back-end programming let’s concentrate on the
front-end design part. At least in this segment, we’ll design our app
first.
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 253
In the next segment, we’ll build the back-end part. So stay tuned.
Another advantage of the ListView is we can place Column, Row
widgets inside it, as our design needs.
First, we have a model class:
1 import 'package:flutter/foundation.dart';
2
3 class ExpenseList {
4 String id;
5 String title;
6 double amount;
7 DateTime date;
8
9 ExpenseList({
10 @required this.id,
11 @required this.title,
12 @required this.amount,
13 @required this.date,
14 });
15 }
Why we’ve done that? Just to display two items at the very
beginning.
Next, we need the build method, where we use ListView to place
our other visible widgets.
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 255
1 @override
2 Widget build(BuildContext context) {
3 return Center(
4 child: ListView(
5 padding: EdgeInsets.all(8),
6 children: [
7 Container(
8 child: Card(
9 child: Text('Chart'),
10 elevation: 10,
11 ),
12 ),
13 Container(
14 child: Column(
15 children: expenseList.map((e) {
16 return Column(
17 crossAxisAlignment: CrossAxisAlignment.start,
18 children: [
19 Row(
20 children: [
21 Container(
22 margin: EdgeInsets.all(8),
23 padding: EdgeInsets.all(8),
24 decoration: BoxDecoration(
25 color: Colors.yellow[100],
26 border: Border.all(
27 color: Colors.red,
28 width: 5,
29 ),
30 ),
31 child: Card(
32 child: Text(
33 '\$${e.amount}',
34 style: TextStyle(
35 fontSize: 20,
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 256
36 fontWeight: FontWeight.bold,
37 ),
38 ),
39 ),
40 ),
41 ],
42 ),
43 ],
44 );
45 }).toList(),
46 ),
47 ),
48 ],
49 ),
50 );
51 }
52 }
1 Column(
2 children: expenseList.map((e) {
3 return Column(
4 crossAxisAlignment: CrossAxisAlignment.start,
5 children: [
6 Row(
7 children: [
8 Container(
9 margin: EdgeInsets.all(8),
10 padding: EdgeInsets.all(8),
11 decoration: BoxDecoration(
12 color: Colors.yellow[100],
13 border: Border.all(
14 color: Colors.red,
15 width: 5,
16 ),
17 ),
18 child: Card(
19 child: Text(
20 '\$${e.amount}',
21 style: TextStyle(
22 fontSize: 20,
23 fontWeight: FontWeight.bold,
24 ),
25 ),
26 ),
27 ),
28 ],
29 ),
30 ],
31 );
32 }).toList(),
33 ),
Inside the Card widget we place a row of items. In the first row we
keep the amount. To add a dollar sign before the amount we have
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 258
1 Column(
2 children: expenseList.map((e) {
3 return Column(
4 crossAxisAlignment: CrossAxisAlignment.start,
5 children: [
6 Card(
7 elevation: 10,
8 child: Row(
9 children: [
10 Container(
11 margin: EdgeInsets.all(8),
12 padding: EdgeInsets.all(8),
13 decoration: BoxDecoration(
14 color: Colors.yellow[100],
15 border: Border.all(
16 color: Colors.red,
17 width: 5,
18 ),
19 ),
20 child: Card(
21 child: Text(
22 '\$${e.amount}',
23 style: TextStyle(
24 fontSize: 20,
25 fontWeight: FontWeight.bold,
26 ),
27 ),
28 ),
29 ),
30 Column(
31 children: [
32 Container(
33 margin: EdgeInsets.all(8),
34 padding: EdgeInsets.all(8),
35 decoration: BoxDecoration(
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 260
36 color: Colors.yellow[100],
37 border: Border.all(
38 color: Colors.red,
39 width: 5,
40 ),
41 ),
42 child: Card(
43 child: Text(
44 '\$${e.amount}',
45 style: TextStyle(
46 fontSize: 20,
47 fontWeight: FontWeight.bold,
48 ),
49 ),
50 ),
51 ),
52 Container(
53 margin: EdgeInsets.all(8),
54 padding: EdgeInsets.all(8),
55 decoration: BoxDecoration(
56 color: Colors.yellow[100],
57 border: Border.all(
58 color: Colors.red,
59 width: 5,
60 ),
61 ),
62 child: Card(
63 child: Text(
64 '\$${e.amount}',
65 style: TextStyle(
66 fontSize: 20,
67 fontWeight: FontWeight.bold,
68 ),
69 ),
70 ),
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 261
71 ),
72 ],
73 ),
74 ],
75 ),
76 ),
77 ],
78 );
79 }).toList(),
80 ),
But, we’re unhappy about one thing. We could have formatted date
in a better way. Although formatting date in flutter is easy.
However, we need to add a package in the flutter dependency.
1 dependencies:
2 flutter:
3 sdk: flutter
4 intl: ^0.17.0
5
6 import 'package:intl/intl.dart';
1 Container displayAmount(ExpenseList e) {
2 return Container(
3 margin: EdgeInsets.all(8),
4 padding: EdgeInsets.all(8),
5 decoration: BoxDecoration(
6 color: Colors.yellow[100],
7 border: Border.all(
8 color: Colors.red,
9 width: 5,
10 ),
11 ),
12 child: Card(
13 child: Text(
14 '\$${e.amount}',
15 style: TextStyle(
16 fontSize: 20,
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 263
17 fontWeight: FontWeight.bold,
18 ),
19 ),
20 ),
21 );
22 }
23 }
1 Column displayTaskAndDate(ExpenseList e) {
2 return Column(
3 crossAxisAlignment: CrossAxisAlignment.start,
4 children: [
5 Container(
6 margin: EdgeInsets.all(5),
7 padding: EdgeInsets.all(8),
8 decoration: BoxDecoration(
9 color: Colors.blue[100],
10 border: Border.all(
11 color: Colors.red,
12 width: 5,
13 ),
14 ),
15 child: Card(
16 child: Text(
17 '${e.title}',
18 style: TextStyle(
19 fontSize: 25,
20 fontWeight: FontWeight.bold,
21 backgroundColor: Colors.blue[100]),
22 ),
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 264
23 ),
24 ),
25 Container(
26 margin: EdgeInsets.all(2),
27 padding: EdgeInsets.all(5),
28 child: Card(
29 child: Text(
30 DateFormat('yyyy/MM/dd').format(e.date),
31 style: TextStyle(
32 fontSize: 20,
33 fontWeight: FontWeight.normal,
34 fontStyle: FontStyle.italic,
35 ),
36 ),
37 ),
38 ),
39 ],
40 );
41 }
1 DateFormat('yyyy/MM/dd').format(e.date),
Moreover, there are other ways of using this date format package
also.
Now, we can use these two methods inside the build method:
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 265
1 @override
2 Widget build(BuildContext context) {
3 return Center(
4 child: ListView(
5 padding: EdgeInsets.all(8),
6 children: [
7 Container(
8 child: Card(
9 child: Text('Chart'),
10 elevation: 10,
11 ),
12 ),
13 Container(
14 child: Column(
15 children: expenseList.map((e) {
16 return Column(
17 crossAxisAlignment: CrossAxisAlignment.start,
18 children: [
19 Card(
20 elevation: 10,
21 child: Row(
22 children: [
23 displayAmount(e),
24 displayTaskAndDate(e),
25 ],
26 ),
27 ),
28 ],
29 );
30 }).toList(),
31 ),
32 ),
33 ],
34 ),
35 );
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 266
36 }
For the complete project code please visit the GitHub repository.
Now, we’re going to add Fruits as our title and the amount is 300.00.
To input the data, we need two text fields first. Next, we need a
button to submit that data, so it gets added to the list.
At the same time, we need to add another functionality. We want
to delete any item from the list as well.
After submitting the data the list gets updated like this:
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 268
1 import 'package:flutter/material.dart';
2 import '../models/expense_list.dart';
3 import '../controllers/display_task_and_data.dart';
4 import '../controllers/display_amount.dart';
5
6 class ExpenseFirstPage extends StatefulWidget {
7 ExpenseFirstPage({Key key}) : super(key: key);
8
9 @override
10 _ExpenseFirstPageState createState() => _ExpenseFirstPage\
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 269
11 State();
12 }
13
14 class _ExpenseFirstPageState extends State<ExpenseFirstPa\
15 ge> {
16
17 ...
18
19 the code is incomplete...
18 });
19 }
For adding the items to the expense list app, we pass two parameters.
One is a string data type and the other is a double data type.
Next we need two text editing controller that flutter supplies.
We’re going to use these text editing controllers inside the Text field
widget. And every action takes place inside the build method.
1 override
2 Widget build(BuildContext context) {
3 return Center(
4 child: ListView(
5 padding: EdgeInsets.all(8),
6 children: [
7 Container(
8 child: Card(
9 child: Text('Chart'),
10 elevation: 10,
11 ),
12 ),
13 Container(
14 child: Card(
15 elevation: 10,
16 child: Column(
17 children: [
18 TextField(
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 271
19 controller: titleController,
20 ),
21 TextField(
22 controller: amountController,
23 ),
24 TextButton(
25 onPressed: () {
26 addTaskAndAmount(
27 titleController.text,
28 double.parse(amountController.text),
29 );
30 },
31 child: Text(
32 'SUBMIT',
33 style: TextStyle(
34 fontSize: 25,
35 fontWeight: FontWeight.bold,
36 ),
37 ),
38 ),
39 ],
40 ),
41 ),
42 ),
43 Container(
44 child: Column(
45 children: expenseList.map((e) {
46 return Column(
47 crossAxisAlignment: CrossAxisAlignment.center,
48 children: [
49 Card(
50 elevation: 10,
51 child: Row(
52 children: [
53 displayAmount(e),
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 272
54 displayTaskAndDate(e),
55 ],
56 ),
57 ),
58 TextButton(
59 onPressed: () {
60 deleteExpenseList(e.id);
61 },
62 child: Text(
63 'DELETE',
64 style: TextStyle(
65 fontWeight: FontWeight.bold,
66 fontSize: 25,
67 ),
68 ),
69 ),
70 ],
71 );
72 }).toList(),
73 ),
74 ),
75 ],
76 ),
77 );
78 }
79 }
Not only we’re going to add items to the list, but at the same
time, we’re going to display them. Furthermore, we should keep
a DELETE button with each item, as well.
Granted, we could have used Icon of the deletion. But, instead
we’ve chosen the Text Button widget.
For managing state and handling the two functions, these two code
snippets are important.
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 273
1 TextField(
2 controller: titleController,
3 ),
4 TextField(
5 controller: amountController,
6 ),
7 TextButton(
8 onPressed: () {
9 addTaskAndAmount(
10 titleController.text,
11 double.parse(amountController.text),
12 );
13 },
14 child: Text(
15 'SUBMIT',
16 style: TextStyle(
17 fontSize: 25,
18 fontWeight: FontWeight.bold,
19 ),
20 ),
21 ),
22
23 ....
24
25 TextButton(
26 onPressed: () {
27 deleteExpenseList(e.id);
28 },
29 child: Text(
30 'DELETE',
31 style: TextStyle(
32 fontWeight: FontWeight.bold,
33 fontSize: 25,
34 ),
35 ),
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 274
36 ),
1 version: 1.0.0+1
2
3 environment:
4 sdk: ">=2.12.0 <3.0.0"
1 import 'package:flutter/material.dart';
2
3 import 'views/fit_fat_app.dart';
4
5 void main() {
6 runApp(FitFatApp());
7 }
1 import 'package:flutter/material.dart';
2
3 import 'fit_fat_first_page.dart';
4
5 class FitFatApp extends StatelessWidget {
6 // This widget is the root of your application.
7 @override
8 Widget build(BuildContext context) {
9 return MaterialApp(
10 title: 'Flutter Demo',
11 theme: ThemeData(
12 primarySwatch: Colors.blue,
13 ),
14 debugShowCheckedModeBanner: false,
15 home: FitFatFirstPage(),
16 );
17 }
18 }
The first page points to the second page. So, now we need that page,
too.
1 import 'package:flutter/material.dart';
2 import '../controllers/fit_fat_text.dart';
3 import '../controllers/display_fit_fat.dart';
4
5 class FitFatSecondPage extends StatelessWidget {
6 @override
7 Widget build(BuildContext context) {
8 return Container(
9 child: ListView(
10 children: [
11 FitFatText(),
12 DisplayFitFat(),
13 ],
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 280
14 ),
15 );
16 }
17 }
As we can see, the view part is finished as the second page imports
two controller widgets.
Consequently, one of the controller widget is stateful, because we
take user inputs, and holds that in memory temporarily. Moreover
that change should reflect on the screen like this:
1 import 'package:flutter/material.dart';
2
3 class FitFatText extends StatelessWidget {
4 const FitFatText({
5 Key? key,
6 }) : super(key: key);
7
8 @override
9 Widget build(BuildContext context) {
10 return Container(
11 margin: EdgeInsets.all(10),
12 padding: EdgeInsets.all(5),
13 decoration: BoxDecoration(
14 shape: BoxShape.rectangle,
15 color: Colors.yellow[100],
16 border: Border.all(
17 color: Colors.blueAccent,
18 width: 2,
19 ),
20 ),
21 child: Text(
22 'Input time and type of exercise and find how many calori\
23 es you burn',
24 textAlign: TextAlign.center,
25 style: TextStyle(
26 fontWeight: FontWeight.normal,
27 fontSize: 20,
28 ),
29 ),
30 );
31 }
32 }
The second controller widget plays the main role in our app. Not
only it takes user’s inputs, processes the data with the help of our
models data class, but it also calculates the calorie, validating the
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 282
1 import 'package:flutter/material.dart';
2 import '../models/fit_fat.dart';
3 import 'type_of_exercise.dart';
4 import 'minute_and_calorie.dart';
5
6 class DisplayFitFat extends StatefulWidget {
7 DisplayFitFat({Key? key}) : super(key: key);
8
9 @override
10 _DisplayFitFatState createState() => _DisplayFitFatState(\
11 );
12 }
13
14 class _DisplayFitFatState extends State<DisplayFitFat> {
15 final List<FitFat> fitFat = [];
16 final exerciseType = TextEditingController();
17 final timeTaken = TextEditingController();
18
19 double burnCalory(String exerciseType, double timeTaken) {
20 double calory;
21 if (exerciseType == 'Running' && timeTaken == 30) {
22 calory = 500;
23 return calory;
24 } else if (exerciseType == 'Walking' && timeTaken == 30) {
25 calory = 300;
26 return calory;
27 } else {
28 calory = 0;
29 return calory;
30 }
31 }
32
33 void takeInput(String exerciseType, double timeTaken) {
34 final exercise = FitFat(
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 283
35 id: DateTime.now().toString(),
36 typeOfExercise: exerciseType,
37 minutes: timeTaken,
38 calory: burnCalory(exerciseType, timeTaken),
39 );
40 setState(() {
41 fitFat.add(exercise);
42 });
43 }
44
45 @override
46 Widget build(BuildContext context) {
47 return Column(
48 children: [
49 Container(
50 child: Card(
51 elevation: 10,
52 child: Container(
53 margin: EdgeInsets.all(10),
54 padding: EdgeInsets.all(5),
55 decoration: BoxDecoration(
56 shape: BoxShape.rectangle,
57 color: Colors.blue[50],
58 border: Border.all(
59 color: Colors.blueAccent,
60 width: 2,
61 ),
62 ),
63 child: Column(
64 children: [
65 TextField(
66 decoration: InputDecoration(
67 border: OutlineInputBorder(),
68 hintText: 'Type Walking or Running',
69 ),
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 284
70 controller: exerciseType,
71 ),
72 TextField(
73 decoration: InputDecoration(
74 border: OutlineInputBorder(),
75 hintText: 'Type 30 to get the exact calory-burn',
76 ),
77 controller: timeTaken,
78 ),
79 TextButton(
80 onPressed: () {
81 takeInput(
82 exerciseType.text,
83 double.parse(timeTaken.text),
84 );
85 },
86 child: Text(
87 'SUBMIT',
88 style: TextStyle(
89 fontSize: 20,
90 fontWeight: FontWeight.bold,
91 ),
92 ),
93 ),
94 ],
95 ),
96 ),
97 ),
98 ),
99 Container(
100 child: Column(
101 children: fitFat.map((e) {
102 return Card(
103 elevation: 15,
104 child: Column(
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 285
105 children: [
106 TypeOfExercise(
107 f: e,
108 ),
109 MinuteAndCalorie(
110 f: e,
111 ),
112 ],
113 ),
114 );
115 }).toList(),
116 ),
117 ),
118 ],
119 );
120 }
121 }
After processing the user’s data and validating it, it also manages
to display the data on the screen. With reference to that, we’ve seen
the image above.
However, if we crammed code of two more displaying widgets
inside, it would look like a spaghetti code.
Flutter always encourages to break your code into small segments.
Therefor, we’ve imported two more widgets.
Watch these lines:
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 286
1 import 'type_of_exercise.dart';
2 import 'minute_and_calorie.dart';
3
4 child: Column(
5 children: fitFat.map((e) {
6 return Card(
7 elevation: 15,
8 child: Column(
9 children: [
10 TypeOfExercise(
11 f: e,
12 ),
13 MinuteAndCalorie(
14 f: e,
15 ),
16 ],
17 ),
18 );
19 }).toList(),
20 ),
1 import 'package:flutter/material.dart';
2 import '../models/fit_fat.dart';
3
4 class TypeOfExercise extends StatelessWidget {
5 const TypeOfExercise({
6 Key? key,
7 required this.f,
8 }) : super(key: key);
9
10 final FitFat f;
11
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 287
12 @override
13 Widget build(BuildContext context) {
14 return Container(
15 margin: EdgeInsets.all(10),
16 padding: EdgeInsets.all(5),
17 decoration: BoxDecoration(
18 shape: BoxShape.rectangle,
19 color: Colors.green[100],
20 border: Border.all(
21 color: Colors.redAccent,
22 width: 2,
23 ),
24 ),
25 child: Text(
26 f.typeOfExercise,
27 style: TextStyle(
28 fontWeight: FontWeight.bold,
29 fontSize: 25,
30 ),
31 ),
32 );
33 }
34 }
1 import 'package:flutter/material.dart';
2 import '../models/fit_fat.dart';
3
4 class MinuteAndCalorie extends StatelessWidget {
5 const MinuteAndCalorie({
6 Key? key,
7 required this.f,
8 }) : super(key: key);
9
10 final FitFat f;
11
12 @override
13 Widget build(BuildContext context) {
14 return Row(
15 children: [
16 Container(
17 margin: EdgeInsets.all(5),
18 padding: EdgeInsets.all(5),
19 decoration: BoxDecoration(
20 shape: BoxShape.rectangle,
21 color: Colors.yellow[100],
22 border: Border.all(
23 color: Colors.blueAccent,
24 width: 2,
25 ),
26 ),
27 child: Text(
28 'Minutes: ${f.minutes}',
29 style: TextStyle(
30 fontWeight: FontWeight.bold,
31 fontSize: 20,
32 ),
33 ),
34 ),
35 Container(
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 289
36 margin: EdgeInsets.all(5),
37 padding: EdgeInsets.all(5),
38 decoration: BoxDecoration(
39 shape: BoxShape.rectangle,
40 color: Colors.yellow[100],
41 border: Border.all(
42 color: Colors.blueAccent,
43 width: 2,
44 ),
45 ),
46 child: Text(
47 'Calory Burned: ${f.calory}',
48 style: TextStyle(
49 fontWeight: FontWeight.bold,
50 fontSize: 20,
51 ),
52 ),
53 ),
54 ],
55 );
56 }
57 }
Still we need a final touch to finish showing our code snippets. The
models folder store the data class that create new instances of user’s
inputs each time we press the submit button.
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 290
1 class FitFat {
2 String id;
3 String typeOfExercise;
4 double minutes;
5 double calory;
6
7 FitFat({
8 required this.id,
9 required this.typeOfExercise,
10 required this.minutes,
11 required this.calory,
12 });
13 }
1 import 'package:flutter/material.dart';
2
3 class FitFatBlankPage extends StatelessWidget {
4 const FitFatBlankPage({Key? key}) : super(key: key);
5
6 @override
7 Widget build(BuildContext context) {
8 return Column(
9 children: [],
10 );
11 }
12 }
1 import 'package:flutter/material.dart';
2 import 'fit_fat_second_page.dart';
3
4 import 'fit_fat_blank_page.dart';
5
6 class FitFatFirstPage extends StatelessWidget {
7 @override
8 Widget build(BuildContext context) {
9 return Scaffold(
10 appBar: AppBar(
11 title: Text('Stay Fit and Burn Fat'),
12 actions: [
13 IconButton(
14 onPressed: () {
15 Navigator.push(
16 context,
17 MaterialPageRoute(
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 293
1 import 'package:flutter/material.dart';
2 import '../controllers/fit_fat_text.dart';
3 import '../controllers/display_fit_fat.dart';
4
5 class FitFatSecondPage extends StatelessWidget {
6 @override
7 Widget build(BuildContext context) {
8 return Scaffold(
9 appBar: AppBar(
10 title: Text('Start Building Exercise Chart'),
11 actions: [
12 IconButton(
13 onPressed: () {
14 Navigator.push(
15 context,
16 MaterialPageRoute(
17 builder: (context) => FitFatSecondPage(),
18 ),
19 );
20 },
21 icon: Icon(Icons.add),
22 ),
23 ],
24 ),
25 body: Container(
26 child: ListView(
27 children: [
28 FitFatText(),
29 DisplayFitFat(),
30 ],
31 ),
32 ),
33 );
34 }
35 }
5. How to handle collections of items, all about List and Map and Code Structure
in Flutter 296
While we’ve changed the Appbar text of the second page, we should
have changed the icon button also. So that either, we can go back
to the previous page, or move forward to the third page.
Of course, you can now do it yourself. Try it. We’ll definitely discuss
navigation and routing in a separate chapter.
For more Flutter related Articles and Resources²⁵
²⁵https://sanjibsinha.com
6. Everything about
Flutter Navigation and
Route
In this chapter we’ll learn how we can navigate from one screen or
page to another screen. How we can pass data from one screen to
another.
We’ll learn about Route and Navigator widgets. What are there
functions and how Flutter provides many features that we can use
to handle navigation.
The full code snippet is available in the respective GitHub reposi-
tory.
The full code repository for this chapter²⁶
Moreover, for updated flutter tutorials don’t forget to visit:
Updated Flutter Tutorials²⁷
Good news is, Flutter has a built-in feature for achieving such a feat.
Moreover, we can control the navigation at the very beginning of
our Flutter app.
To do this, in flutter MaterialApp widget, we use onGenerateRoute
property.
Material design and material components play a key role in build-
ing a Flutter app. In addition, the material design system unites
style, branding, interaction and motion using a set of material
components.
And these material components work under material design’s
principles. That makes the usage of onGenerateRoute property in
flutter.
1 import 'dart:ui';
2
3 import 'package:flutter/material.dart';
4
5 class MaterialDesign extends StatelessWidget {
6 const MaterialDesign({Key? key}) : super(key: key);
7
8 @override
9 Widget build(BuildContext context) {
10 return MaterialApp(
11 title: 'Better Flutter - Essential Widgets',
12 home: MDFirstPage(),
13 initialRoute: '/second',
14 onGenerateRoute: _getSecondPageFirst,
15 );
16 }
17
18 Route<dynamic>? _getSecondPageFirst(RouteSettings setting\
19 s) {
20 if (settings.name != '/second') {
21 return null;
22 }
23
24 return MaterialPageRoute<void>(
25 settings: settings,
26 builder: (BuildContext context) => MDSecondPage(),
27 fullscreenDialog: true,
28 );
6. Everything about Flutter Navigation and Route 300
29 }
30 }
Watch the above code. Although the home property indicates to the
first page, the initialRoute property navigates to the second page.
However, we need to be careful about one thing. It should return a
non-null value.
The rest is quite simple. Now we can design our first page and
second page.
In any case, the app will open the second page first.
If we want to make this page a log in and registration page, we can
design that too.
In addition, if the user doesn’t want to log in or register, she can
touch the cross icon. In that case, the home property comes into
effect, opening the first page as usual.
To begin with, we’ll see how a navigator and routes work in Flutter.
Firstly, we want a simple example. So the beginners can understand.
Moreover, a better flutter developer must understand how routes
work.
Secondly, to keep it simple, we navigate from first screen to the
second.
Further, we’ll learn how we can handle multiple routes. Above all,
how we can pass data while navigating to a second page.
As we have said, route is a widget. Navigator is also another widget.
1 import 'package:flutter/material.dart';
2
3 void main() {
4 runApp(const MyApp());
5 }
6
7 class MyApp extends StatelessWidget {
8 const MyApp({Key? key}) : super(key: key);
9
10 // This widget is the root of your application.
11 @override
12 Widget build(BuildContext context) {
13 return MaterialApp(
14 routes: <String, WidgetBuilder>{
15 '/': (BuildContext context) => const HomePage(),
16 },
17 title: 'Flutter Demo',
18 theme: ThemeData(
19 primaryColor: const Color(0xFF3EBACE),
20 backgroundColor: const Color(0xFFF3F5F7),
21 primarySwatch: Colors.indigo,
22 ),
23 );
24 }
25 }
As we can see, the route widget takes us to the Home Page. However,
it also carries the context.
Next, we need to go to the second page.
36 }
37 }
As we can use the Gesture Detector, we can now tap the image. And
that takes us to the second page.
6. Everything about Flutter Navigation and Route 307
Although we’ve not used Navigator pop method in the second page,
yet we can come back to the first page using the back button in the
App Bar.
click any one of the category, it will take us to the detail page.
1 import 'package:flutter/material.dart';
2
3 class Category {
4 final String id;
5 final String title;
6 final Color color;
7
8 const Category({
9 required this.id,
10 required this.title,
11 this.color = Colors.orangeAccent,
12 });
13 }
14 And with the category class, we need some dummy category \
15 data like the following code.
16
17 import 'package:flutter/material.dart';
18
19 import 'category.dart';
20
21 const DUMMY_CATEGORIES = const [
22 Category(
23 id: 'c1',
24 title: 'Health',
25 color: Colors.red,
26 ),
27 Category(
28 id: 'c2',
29 title: 'Wellness',
30 color: Colors.deepOrange,
31 ),
32 Category(
33 id: 'c3',
34 title: 'Politics',
35 color: Colors.black54,
6. Everything about Flutter Navigation and Route 313
36 ),
37 Category(
38 id: 'c4',
39 title: 'Travel',
40 color: Colors.green,
41 ),
42 Category(
43 id: 'c5',
44 title: 'Internet',
45 color: Colors.yellow,
46 ),
47 Category(
48 id: 'c6',
49 title: 'Lifestyle',
50 color: Colors.indigo,
51 ),
52 Category(
53 id: 'c7',
54 title: 'Headlines',
55 color: Colors.pink,
56 ),
57 Category(
58 id: 'c8',
59 title: 'Sports',
60 color: Colors.orange,
61 ),
62 Category(
63 id: 'c9',
64 title: 'Science',
65 color: Colors.blueAccent,
66 ),
67 Category(
68 id: 'c10',
69 title: 'Environemnt',
70 color: Colors.redAccent,
6. Everything about Flutter Navigation and Route 314
71 ),
72 ];
Since the related code is too long, please visit the respective GitHub
repository. Above all this repository also connects you to the book
Better Flutter in Leanpub.
This repository will also give an idea how from the current route
we move to the detail page like the following image.
6. Everything about Flutter Navigation and Route 315
As we can see that the current route takes us to the detail page
where the data are coming from the dummy News class.
If we take a look at the News class, we will understand how it works.
6. Everything about Flutter Navigation and Route 316
1 enum Nature {
2 hard,
3 soft,
4 }
5
6 class News {
7 final String id;
8 final List<String> categories;
9 final String title;
10 final String detail;
11 final String imageURL;
12 final Nature nature;
13
14 const News({
15 required this.id,
16 required this.categories,
17 required this.title,
18 required this.detail,
19 required this.imageURL,
20 required this.nature,
21 });
22 }
At the same time, we pass the data from page one to the second
page like this:
1 body: GridView(
2 gridDelegate: const SliverGridDelegateWithMaxCros\
3 sAxisExtent(
4 maxCrossAxisExtent: 200,
5 crossAxisSpacing: 20.0,
6 mainAxisSpacing: 20.0,
7 ),
8 children: DUMMY_CATEGORIES.map(
9 (e) {
6. Everything about Flutter Navigation and Route 317
10 return AllCategories(
11 id: e.id,
12 title: e.title,
13 color: e.color,
14 );
15 },
16 ).toList(),
17 ),
18
19 // code is incomplete for brevity, please consult the Git\
20 Hub repository
1 routes: {
2 '/': (context) => const FirstPageBody(),
3 '/categories': (context) => SecondPage(),
4 },
To get the data the modal route class uses a static method “of” that
passes the context. And the context also defines the location of this
widget in the widget tree.
In the coming flutter tutorials we’ll discuss more about passing data
through route and context.
Subsequently, we’re going to build the News app and this time we’ll
use enum as a special type. We’ll also learn how to pass enum along
with other data while we navigate to another page.
a soft News story that people might save and read later.
Our Enums will serve that purpose. So we add that to the News
class first.
1 enum Nature {
2 hard,
3 soft,
4 }
5
6 class News {
7 final String id;
8 final List<String> categories;
9 final String title;
10 final String detail;
11 final String imageURL;
12 final Nature nature;
13
14 const News({
15 required this.id,
16 required this.categories,
17 required this.title,
18 required this.detail,
19 required this.imageURL,
20 required this.nature,
21 });
22 }
Next, we’ll use the Enums in the display page as string data. To use
Enums as string data we need to use switch case.
6. Everything about Flutter Navigation and Route 323
Now, at the top of the display page, we can use that Enums like the
following images.
1 body: ListView.builder(
2 itemBuilder: (context, index) {
3 return Column(
4 children: [
5 Container(
6 margin: const EdgeInsets.all(10.0),
7 padding: const EdgeInsets.all(10.0),
8 child: Text(
9 natureText,
10 style: const TextStyle(
11 fontSize: 20.0,
12 ),
13 ),
14 ),
15
16 // code is incomplete for brevity
Hopefully, this makes sense. Moreover, we’ll not stop here. We’ll
build the News app so that it looks more attractive than this.
That means, we’ll design our Flutter News app better than this one.
And at the same time, to pass data we’ll use route and navigation
in a different way.
6. Everything about Flutter Navigation and Route 325
1 theme: ThemeData(
2 primarySwatch: Colors.pink,
3 primaryColor: Colors.amber,
4 canvasColor: const Color.fromRGBO(255, 254, 229, \
5 1),
6 fontFamily: 'Raleway',
7 textTheme: ThemeData.light().textTheme.copyWith(
8 bodyText2: const TextStyle(
9 color: Color.fromRGBO(20, 51, 51, 1),
10 ),
11 bodyText1: const TextStyle(
12 color: Color.fromRGBO(20, 51, 51, 1),
13 ),
14 headline6: const TextStyle(
15 fontSize: 20,
16 fontFamily: 'RobotoCondensed',
17 fontWeight: FontWeight.bold,
18 )),
19 ),
How do we can use this route name? To do that we need to use the
route widget in our Material App widget.
In case, our route name doesn’t work, we can also use a fallback.
6. Everything about Flutter Navigation and Route 329
1 onUnknownRoute: (settings) {
2 return MaterialPageRoute(
3 builder: (ctx) => const CategoriesScreen(),
4 );
5 },
17 appBar: AppBar(
18 title: Text(categoryTitle!),
19 ),
20
21 // code is incomplete for brevity
1 const dummyNews = [
2 News(
3 id: 'b1',
4 categories: [
5 'c1',
6 'c4',
7 ],
8 title: 'Global Worming fuels disaster',
9
10 ....
11
12 News(
13 id: 'b5',
14 categories: [
15 'c1',
16 'c5',
17 ],
18 title: 'Take more outdoor walks',
19
20 // code is incomplete
6. Everything about Flutter Navigation and Route 331
1 Route
2 Navigator
3 List and Map
36 return MaterialPageRoute(
37 builder: (ctx) => const CategoriesScreen(),
38 );
39 },
40 );
41 }
42 }
1 body: GridView(
2 padding: const EdgeInsets.all(25),
3
4 /// the first page displays CategoryItem controll\
5 er
6 children: dummyCategories
7 .map(
8 (catData) => CategoryItem(
9 id: catData.id,
10 title: catData.title,
11 color: catData.color,
12 ),
13 )
14 .toList(),
15
6. Everything about Flutter Navigation and Route 335
16 ...
17 // code is not complete
The dummy categories are a few constant data where we’ve defined
the id, title and the color of the category.
1 const dummyCategories = [
2 Category(
3 id: 'c1',
4 title: 'Health',
5 color: Colors.red,
6 ),
7 Category(
8 id: 'c2',
9 title: 'Wellness',
10 color: Colors.deepOrange,
11 ),
12 ....
13 // code is not complete
Actually, the Category Item widget will help to display all the
categories and at the same time it will let us allow to click each
category to see what kind of news items belong to that category.
12
13 void selectCategory(BuildContext ctx) {
14 Navigator.of(ctx).pushNamed(
15 CategoryNewsScreen.routeName,
16 arguments: {
17 'id': id,
18 'title': title,
19 },
20 );
21 }
22 ...
23 // code is not complete
We’ve passed all data through the Class Constructor and then we
define a method select Category.
Inside that method, Navigator widget uses a chain of static methods
through which we pass the context and a list of arguments.
Because of this widget we can now see all the categories.
However, if we click any category the method fires and push the
Navigator to another stateless widget Category News Screen.
Let us see a few part of Category News Screen widget code.
Next, we click any category and reach the News Item that belongs
to that Category.
Now we wan to see the News detail page.
19 title: Text(categoryTitle!),
20 ),
21 body: ListView.builder(
22 itemBuilder: (ctx, index) {
23 return NewsItem(
24 id: categoryNews[index].id,
25 title: categoryNews[index].title,
26 imageUrl: categoryNews[index].imageURL,
27 nature: categoryNews[index].nature,
28 );
29 },
30 itemCount: categoryNews.length,
31 ),
32 );
33 }
34 }
1 child: Column(
2 children: <Widget>[
3 Stack(
4 children: <Widget>[
5 ClipRRect(
6 borderRadius: const BorderRadius.only(
7 topLeft: Radius.circular(15),
8 topRight: Radius.circular(15),
9 ),
10 child: Image.network(
11 imageUrl,
6. Everything about Flutter Navigation and Route 340
12 height: 250,
13 width: double.infinity,
14 fit: BoxFit.cover,
15 ),
16 ),
17 Positioned(
18 bottom: 20,
19 right: 10,
20 child: Container(
21 width: 300,
22 color: Colors.black54,
23 padding: const EdgeInsets.symmetric(
24 vertical: 5,
25 horizontal: 20,
26 ),
27 child: Text(
28 title,
29 style: const TextStyle(
30 fontSize: 26,
31 color: Colors.white,
32 ),
33 softWrap: true,
34 overflow: TextOverflow.fade,
35 ),
36 ),
37 )
38 ],
39 ),
40 Padding(
41 padding: const EdgeInsets.all(20),
42 child: Row(
43 mainAxisAlignment: MainAxisAlignment.spac\
44 eAround,
45 children: <Widget>[
46 Row(
6. Everything about Flutter Navigation and Route 341
47 children: <Widget>[
48 const Icon(
49 Icons.work,
50 ),
51 const SizedBox(
52 width: 6,
53 ),
54 Text(natureText),
55 ],
56 ),
57 ],
58 ),
59 ),
60 ],
61 ),
The above method with the help of “on Tap” void function return
the “select News” method with the context as its parameter.
What Next?
If you find these information useful, I am happy. For any Flutter
related query, you may send an email to [email protected].
And at the same time don’t forget to visit the website where I write
regularly on Flutter only.
6. Everything about Flutter Navigation and Route 345
You’ll get the updated Flutter tips and tricks. So how about take the
trips? :)
Dedicated to updated Flutter articles²⁸
²⁸https://flutter.sanjibsinha.com