Flutter Notes
Flutter Notes
Flutter Notes
Flutter is a SDK that allows to build native cross platform (iOS, android) apps with one
programming language and codebase.
Flutter architecture
All information about the architecture of flutter is given here -
https://flutter.dev/docs/resources/technical-overview
Flutter Does NOT use platform primitives (Advantage over React native or any other cross
platform alternative)
This means actually that unlike react native (in which code is compiled into native
equivalents or native alternative. Like to render a button, react native code may be
converted to the native code like UIButton for iOS or widget. Button for android), flutter
provides its custom implementation by providing its own engine which controls every
pixel drawn on the screen thereby fully controling the whole screen giving flutter –
a. Better Control – because now we don’t have to worry about limitations while
converting into native code. We have access to the whole screen and thus we can
create more freely with less limitations.
b. Lot better Performance – (search more on this)
Installing Flutter
To install flutter is easy. Just follow the doc here - https://flutter.dev/docs/get-
started/install/windows. There are 4 main pre-requisites (as specified by flutter doctor) for
flutter –
a. Flutter - This includes following three things (all of which are given in doc)–
i. Installing git as pre-requisite
ii. Then downloading flutter SDK
iii. Then setting the flutter path for using flutter from cmd
b. Android SDK – This was the main sticking point where you wasted time. There are 2
methods of doing this –
I. Automatically Via Android Studio (preferred)
You did not want to use android studio (because compared to VS code it
takes up lot of resources and thus slows PC down and also uses 1.5 GB extra
storage for the IDE) but it is the best way because it is very easy & time
saving due to allowance to manage SDK and AVD (for creating virtual
device) through GUI.
II. Manually Via Android SDK command line tools
This can be downloaded from the site itself. But the problem is that
everything has to be done through command line (older versions provided an
application(.exe) to manage SDK and AVD but it is now obsolete and in latest
version of SDK tools there was no .exe files, to force you to install android
studio, and thus everything had to be done using cmd) of which you have no
experience and internet lacks resources for it and thus will take a lot of time
just to install the SDK. But to install SDK in this way, go through the below
links –
Google search - installing android sdk without android studio
https://www.maketecheasier.com/install-android-sdk-in-windows/
c. IDE – You need to install VS Code or Android studio to code on dart. You will use VS
Code because it is much lighter and less resource intensive than Android Studio. So
install VS code and install flutter and dart extensions in it.
d. Device – This the device on which the app will run. There are 2 options –
I. External Device – use your phone by enabling USB Debugging. This is a
marginally better because it will not take up the resources of your PC which
the emulator will take (but in New PC it won’t make much of a difference).
II. Emulator – For this, you need to create new device from AVD Manager in
Android studio. Make a new custom device (don’t choose from prebuild
devices like pixel as they are more resource intensive and you can’t change
many of their advanced settings) so that you can create a light device using
less resources of your PC and this also allows to use GPU acceleration
resulting in a better performance.
Creating & Running Flutter programs
To follow these steps, you need to run 2 CMD commands as given below –
a. To create just go to the directory where you want store the app and run command –
flutter create <app_name>
b. To Run navigate to the app directory and run command –
flutter run
class Info {
String name;
int age;
Info({String uName, int uAge = 0}) {
name = uName;
age = uAge;
}
}
int main() {
Info u1 = Info(uName: "Nikhil");
Info u2 = Info(uName: "Sakshi", uAge: 16);
Info u3 = Info(uAge: 15, uName: "Makhan");
}
In the above example – uName and uAge are named argument. These are mainly used for 2
things –
a. To provide optional arguments while calling the function – Like while create u1
object above, it was optional for us to skip the uAge argument (in which case the
default value assigned to uAge i.e. 0 is stored as age of nikhil). So, we can pass as
many arguments as we want and to tell the argument is for which parameter just
pass argument in the following the syntax - <parameter_name: argument_value>
b. To Pass the arguments in any order – this is very useful for functions with lot of
parameters because while calling the function we won’t have to remember the order
in which to pass the arguments.
NOTE – in flutter we can make some of the parameters required while some parameters
optional by using @required before the required parameter and if the argument for that
parameter is not provided while calling the function then there will be a compiler error.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(home: Text("Hello World!"),);
}
}
Code Explanation -
Now the create a hello world app, we need to display a text onto the screen. Now as
everything in flutter is a widget, this hello world text will also be a widget. But now
what is a widget programmatically in flutter – a widget is an object. So, we will need
to create a class to create or use a widget. Let’s call this class MyApp class.
Now a widget is a portion of UI so for creating a widget we will need to handle pixels
of the UI which is not trivial. Thankfully its already done by flutter using build
method of stateless widget class. So, to get this method in our MyApp class, we need
to extend the stateless widget class.
Now we can easily use build function in our class to build & return a widget. As we
will provide our definition own of build function (to build a hello world widget), we
have to override the build function and overriding means taking same parameters &
return same type as defined in Base Class function definition. As we get a widget in
return, we return an object of Widget class. And it takes as argument a BuildContext
object (it will some metadata about the widget, its position in the widget tree and
the overall application. Search more about this).
But how to actually make the hello world widget? For that also flutter provides a
class called MaterialApp which wraps various widgets (passed through its
constructor) into single widget required for an app using material design.
Now this function provides various parameters which are optional. But how can
flutter recognise the argument which we pass is meant for which parameter. So,
flutter provides named parameters. So now will pass a built-in text widget as follows
text(‘Hello Word!’) to create the text widget for home: parameter and MaterialApp
will wrap it into a single widget and return it.
Now to display this widget (i.e. actual work of painting or rendering on screen) we
call runApp() in which our MyApp widget object has to passed. The widget object
will be used to call the build() given in MyApp to build the widget and then the
build-up widget will get painted onto the screen (the painting is done by runApp
because it uses a class which is extending some rendering class. And the rendering
class does the actual drawing in screen - https://medium.com/manabie/how-flutter-
renders-widgets-fd6eca945a04#:~:text=Flutter%20will%20go%20through%20your,when
%20Element%20calls%20createRenderObject(). Or offline).
Now the above hello world App can be beautified by making the below changes to the build
function (Using scaffold()) –
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Hello World App')),
body: Text('Hello World!'),
),
);
}
}
Code Explanation –
The main part in the modified code is using scaffold widget. Scaffold helps in
implementing the basic material design visual layout structure.
But how? By passing widgets to the named arguments provided by it. Scaffold will
create a basic app widget by combining the widgets provided as arguments. So, in
above we pass we use 2 of its named parameters - appBar & body.
appBar needs a widget which will be displayed as app bar. So, for creating the app
bar also, flutter provides an inbuilt widget called – AppBar().
In the main body we want to display the hello world message so it means that in
body: parameter we just need to pass text widget with our message.
(DART) Lists
List are dart equivalent of arrays. They are implanted using classes and the syntax is as
follows-
List<type> list_name = [list_data] (creates dynamic array i.e. Elements can be added later)
Or
(creates static array i.e. elements can’t be added later)
Actually, dart doesn’t have your typical access modifiers (public, private, protected). Instead
it just uses ‘_’ which makes member method, data member, or class as file public (that is it
will be visible in the file only. For other files it is private). In dart this called private access
modifer. Read this in detail in this doc - offline.
BEWARE - Now in course, Max makes a class private and then makes its members also
separately private. But you had doubt that what is the need of creating a member
separately private when class itself is private. I mean if we can’t create instance of class or
extend it outside the file then its members can’t be accessed and thus, they implicitly
become private but consider the following case
https://stackoverflow.com/questions/53495089/dart-should-the-instance-variables-be-private-or-
public-in-a-private-class –
(DART) Multiple constructors
In dart, we can create multiple constructors with different names this is shown in the
following code snippet –
In the above we create 2 constructors and 2 nd constructor is a named constructor. Now
nothing new just remember the syntax – if before a function we have the class name and
dot then it is a named constructor.
Look at what Function class does and What typedef cb = void Function(paramters) does.
Look into detail how function pointer (passed through callback) is called in flutter.
(DART) Maps
The Map object is a simple key/value pair. Keys and values in a map may be of any type. A
Map is a dynamic collection. In other words, Maps can grow and shrink at runtime.
Maps can be declared in two ways –
Using Map Literals –
To declare a map using map literals, you need to enclose the key-value pairs within a
pair of curly brackets "{ }".
Here is its syntax −
var identifier = { key1:value1, key2:value2 [,
…..,key_n:value_n] }
An example (maybe pointer internally as they are in c++ because use case is exactly same as
demonstrated below Bute still search more) –
(DART) Collection
Collection is an object that represents a group of objects, which are called elements. So, this
just an array but storing objects within it (instead of a data type).
Now list nums is a collection because it is an object (of List class) and the object will store 3
objects of class int (yes, int is a class in dart).
(DART) Iterable
Now the most basic collection in dart is Iterable which stores elements in such a way
that they can be accessed sequentially only. This is proved by below code –
Iterable<int> iterable = [1, 2, 3];
int value = iterable.elementAt(1);
You can instead read elements with elementAt(), but internally it steps through the
elements of the Iterable until it reaches that position which is sequential access (not
random access) and op [] isn’t defined for Iterable so you can only access elements
sequentially.
The elements of the Iterable are accessed by getting an Iterator using the iterator
constructor, and using it to step through the values (in c++ iterator is pointer so this
may also be pointer it is helping in sequential access which pointer also does).
Stepping with the iterator is done by calling Iterator.moveNext, and if the call
returns true, the iterator has now moved to the next element, which is then
available as Iterator.current.
List (which is collection storing elements in such a way that random access can be
done) and Set (storing objects which come only once inside the collection) are
modified Iterable because they extend Iterable class. Whereas Map, even though
using a different structure internally, is Iterable because its keys are Iterable (search
more on this). Most classes in the dart:collection library as also Iterable.
Iterable is an abstract class and thus can’t be instantiated directly but by assigning
list or Set object to create its instance like given below –
Iterable<int> iterable = [1, 2, 3];
1. final is mainly used as runtime constant and const is compile time constant –
A compile-time constant is a value that can be (and is) computed at compile-time. A
runtime constant is a value that is computed only while the program is running. If
you run the same program more than once:
a. A compile-time constant will have the same value each time the application is
run.
b. A runtime constant can have a different value each time the application is
run.
2. (Extension of above) Both final and const prevent a variable from being reassigned
(similar to how final works in Java or how const works in JavaScript).
The difference has to do with how memory is allocated. Memory is allocated for a
final variable at runtime, and for a const variable at compile-time.
Mainly that a final variable may be an instance variable, but a const must be a static
variable on a class. This is because instance variables are created at runtime, and
const variables--by definition--are not. Thus, const variables on a class must be
static (they can’t be used otherwise whereas final can be used without static),
which means simply that a single copy of that variable exists on a class, regardless of
whether that class is instantiated.
(Note – final can also be used for declaring compile-time constants it is just that
const is preferred for it because that is the definition of const and when 2 things can
achieve the same task, you should use prefer concept which is defined for the
particular task).
3. Now, when everything which can be done using const can also be done using final
(cause final can also declare compile time constants) then why use const? Actually,
there is one thing that final can’t do that is make object stored in it also final. See,
const modifies values whereas final modifies variables. So, when we use const
on a variable the object stored in var also becomes a constant and thus
completely immutable. Whereas when we use final on a variable, the stored
value/object in the variable doesn’t become constant. We can’t reassign but that
doesn’t mean that the object already stored itself can’t be modified i.e. we can
change the fields of the object which is stored in a final variable thus it is not
completely immutable.
So, both const and final cannot be reassigned, but fields of the object being stored
in a final variable, as long as they aren't const or final, can be reassigned (unlike
const).
void main() {
final listA = [5, 6, 7,];
listA.add(8);//perfectly fine
print(listA[3]);//prints 8 meaning 8 is added to list
/*But the above same is not doable with const (which is a good thin
g as it will make list Completly immutable)
final listA = [5, 6, 7,];
listA.add(8);//This will give error as const will make value also c
onst
*/
}
Initializing inside constructor body gives error (as shown in below comment).
(Search MORE on GOOGLE - initializing final in flutter) –
Quiz(this._qAnda, this._indx, this._c);
/*IMPORTANT - see why the below dosen't work but above one works
Quiz(List<Map<String, Object>> qAnda, int indx, Function _c){
_qAnda = qAnda; //This will throw an error that final can’t be
initialized here
}*/
One reason for above can be (your speculation) just that dart doesn’t allow use of
‘=’ op on final variable but with the 1st way we aren’t using any equal operator thus
dart can handle the 1st way and thus allows it. The value will be put into memory
when the object is created.
(DART) GETTER
class Result extends StatelessWidget {
final _totalScore;
String get resultPhrase {
if (_totalScore <= 5)
return 'You are Innocent';
else if (_totalScore <= 10)
return 'You are Casual';
else
return 'You are Edgy';
}
Result(this._totalScore);
@override
Widget build(BuildContext context) {
return Text(resultPhrase);
}
}
As you can see in above syntax – syntactically you can note the following –
a) Defined like a function - with keyword get, it is necessary to give a return type for it.
No parenthesis after getter name because it is a method which can never receive any
argument (It just gives, never receives anything). And it also has a body so it has 2 of
3 things a function definition has (return type, parameters, & body).
b) Used like a property - we just used it inside Text widget just like any data
member/variable/property.
This shows mainly the how to place multiple widgets into a widget & working of a button.
The main features explained are –
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() { return MyAppState(); }
}
class MyAppState extends State<MyApp> {
int indx = 0;
var ques = ["what's your favourite animal", "what's your favourite colour"];
void changeQuestion() {
setState(() { // changing state to Re-render UI
indx = (indx + 1) % 2;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Q&A App')),
body: Center(
child: Column(
children: [
Text(ques[indx]),
RaisedButton(
child: Text("ANS 1"),
onPressed: changeQuestion,
),
RaisedButton(
child: Text("ANS 2"),
onPressed: changeQuestion,
),
RaisedButton(
child: Text("ANS 3"),
onPressed: changeQuestion,
),
],
),
),
),
);
}
}
Now, Stateful widgets are those in which - data inside the widget can change. So, it
means that any widget class which has a data member which can change inside the
class and that change is persisted (i.e. the change must be maintained in the next
object created) has to be a stateful widget. So, internal state is just a static data
member. In this app, we have question index (int indx) as a data member whose
value is changed inside the class (when a button is pressed), then this class must be a
stateful widget.
This is taught in course also – a Stateful widget has some internal state which can
change whereas Stateless widget’s internal state doesn’t ever change (main
difference). This internal state is nothing but data member value defined inside the
class. Now there can also be an external state (That means the member(s) receive
the data in data member from outside the class by passing values using constructor
i.e. when you get data as input from user while creating the object). Both widgets
can have external state changed.
Now, you did not quite understand how to create stateful widgets and thus your
doubts are given in NOTE BELOW. READ IT TO MAEK FURTHER NOTES.
Now, whenever any state is changed, flutter re-renders the UI (this is done by
explicitly calling setState() method to indicate to flutter that out state has changed
so you should re-render the UI.
Note - word ‘explicitly’ is important. If we change data without using setState() UI
won’t be re-rendered.
We use setState() by passing a function into it (call-back) which contains the logic
for state change.
setState() forces flutter to re-render the UI by calling the build method of the class
in which it is used. build() re-builds the widget tree but the whole UI is not re-
rendered. Instead just that part which is changed is re-rendered and this done
using the element and render object tree).
* NOTE - Actually, you don’t quite understand the method of creating Stateful
Widgets maybe because the whole idea of how dart manages the stateful widgets is
not known to you thus you have doubt like below –
1. (Main Doubt) why to have 2 classes for creating stateful class? Now if we
change data member inside the class that change should be persistent i.e. if we
create a new widget object the data changed through previous widget has to
persist and thus the new object should also have changed internal state value.
So, in oops, you would have done this by making the internal state data
member as – static. But we can’t do the same here because we are working with
a framework and thus, we have to work how it wants us to work. Actually, one
reason can be that (Search actual reason) – because flutter maintains a widget,
element, and render object tree it may need to rebuild one of the trees and thus
it can’t just let us make a static variable and infer from it when to re-render UI.
2. (Doubt) why flutter makes us create 2 classes for stateful widgets (why can’t
we have static members instead inside the class. And if there is a static
member then we have to define it as stateful widget) and how they are
connected to each other.
3. (Doubt) Intuitively understand the method to create a stateful
widget programmatically in flutter i.e. -
a. (Doubt) how runApp() calls createState() (function) to
create and manage states and how widgets get build (i.e.
how the build method gets executed by runApp())
b. (Doubt) How setState() function works under the hood in
flutter.
c. (Doubt) what is State<MyApp> (it is maybe a generic class
so search about it).
Look at this link if it helps - https://proandroiddev.com/flutter-a-hitchhiker-guide-to-
stateless-and-stateful-widgets-cc9f9295253b.
So, for now just remember the method to create stateful widgets (as taught in
course) and later understand how flutter (framework) manages the stateful widgets
so that you can deeply understand why we need to create stateful widgets the way
we create them.
Now we better the code (using conventions), beautify the app a bit more, and change text
of the answer buttons according to question by following code changes –
main.dart
import 'package:flutter/material.dart';
//Leave above one line to import your own packages
import 'package:flutter_basics_app/answer.dart';
import 'package:flutter_basics_app/ques_ans_text.dart';
import 'package:flutter_basics_app/question.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return MyAppState();
/* Method 2 Code
return MyAppState([ ['Cow', 'Rabbit', 'Cats'], ['Blue', 'White'] ]);*/
}
}
class MyAppState extends State<MyApp> {
int indx = 0;
/*Method 1 - List of QuesAnsText objects
var qAnda = [
QuesAnsText( "what's your favourite Animal", ["Cow", "Rabbit", "Cats"] ),
QuesAnsText("what's your favourite Colour", ["Blue", "White", "Black"] ),
];*/
//Method 2 - using a Map() object
var qAnda = [
{
"question": "what's your favourite animal",
"answer": ["Cow", "Rabbit", "Cats"],
},
{
"question": "what's your favourite Colour",
"answer": ["Blue", "White", "Black"],
},
];
/*Method 3 - 2-D list having answers list according to question indx
var ans = List.generate(2, (i) => List<String>(3));
MyAppState(this.ans);*/
void changeQuestion() {
setState(() {
indx = (indx + 1) % 2;
});
}
@override
Widget build(BuildContext context) {
/* Method 3 code. (Exact same for method 2 just with little changes to ans
wers from 2-D list)
List<Widget> answerButtons = new List<Widget>();
for (int i = 0; i < qAnda[indx].getAnsLen(); i++) {
answerButtons.insert(i, Answer(qAnda[indx].getAnsI(i), changeQuestion));
}*/
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Q&A App')),
body: Center(
child: Column(
children: [
Question(qAnda[indx]['question']),
...(qAnda[indx]['answer'] as List<String>).map((answerText) {
return Answer(answerText, changeQuestion);
}).toList(),
/*Method 2 & 3 Code
...answerButtons,*/
],
),
),
),
);
}
}
answer.dart
import 'package:flutter/material.dart';
//Answer widget builds a RaisedButton widget contiang ans text
class Answer extends StatelessWidget {
final String text;
@required
final Function callback;
Answer(this.text, this.callback);
@override
Widget build(BuildContext context) {
return Container(
width: 250,
child: RaisedButton(
child: Text(text),
color: Colors.blue,
textColor: Colors.white,
onPressed: callback,
),
);
}
}
question.dart
import 'package:flutter/material.dart';
//Answer widget builds a Container widget contiang question text
class Question extends StatelessWidget {
final String que;
Question(this.que);
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
child: Text(
que,
style: TextStyle(fontSize: 20),
textAlign: TextAlign.center,
),
);
}
}
import 'answer.dart';
import 'question.dart';
(NOTE – until you don’t get the practice, make your code first into the main.dart
file and later shift the code in other files. This is so that you don’t get confused to
get the logic and shift between files to know what is happening. This is also what
happens in course. He makes code in main.dart dart and later on shifts code in
different files and makes changes accordingly)
Second, we use final keyword for our data members or states of our Stateless
widgets because our data members can’t be changed from inside the class. They can
only be changed externally through constructor when a new object is created.
For this we start by increasing the font size of the question. This can be done by the
using the named parameter called style: it takes as argument a TextStyle widget.
Then let’s centre the text. This can be done by Centre widget also but in course there
was also a different approach. In this approach we first use the named parameter
called textAlign: It can be provided an Enum called TextAlign.center which aligns the
text in centre. But using it alone won’t make text centred because by default Text
widget only provides as much space of screen as needed by text. But then how to
give the text full width of the screen. This can be done by enclosing it in a Container
widget which in-turn has a named parameter called width: and setting its value to
double.infinity you give the container the full width of the screen. Now when the
above Text widget will be provided to the child: named parameter the text widget
will also have the full width of the screen and then the text appear centred.
Note – Container widget has the following layout on screen –
Now let’s Style our button (in answer.dart file) in the following ways –
To add colour – we can give colour through named para color: in following ways –
a. Either we can create a Color widget and in it pass the colour value as follows –
color: Color(Colors.lightBlue[300].value)
b. (course method) to give a basic colour, Colors class has static members which
store the hex value of the colour So we can use them.
Now it is better than above because we don’t instantiate the object of colour as
we can call static members without create objects of the class in the following
syntax –
color: Colors.blue,
NOTE – This syntax may seem similar to Enums but the difference Enums can
only store numbers whereas a static member is a variable and thus can store
different datatype values like here itself blue is storing hex code of blue colour.
Make button wider – Now you can also make the button look bigger with 2 ways –
a. One way is to use the named parameter called padding: and add padding to left
and right using EdgeInsets.only(left: value, right: value)
b. (course method) We can simply enclose out button within a container and give
the container the full width of screen (using double.infinity). This will also make
button wide.
Now let’s change text of the answer buttons according to change in question (in
main.dart)–
Before moving on a note on a topic - Lifting the state up – This came very naturally
to you (because it’s quite logical) but still for understanding.
So, this lifting the state up concept tells to store the state into the parent widget
instead of the widget using the state.
This can be understood by the analogy of SUN 🌞
In the world, everyone can see (observe) or feel (listen) the sun because always
located above us i.e lifting the state up.
This is what you did here, indx state will be used by answer.dart (to update the index
so that it increments and shows the next question) as well as question.dart (to know
which question to pass into the Question widget constructor). So, we stored the indx
state inside their parent MyApp widget (actually inside the MyAppState class but
this is attached to MyApp class so it is indirectly inside the MyApp widget class) so
that it is accessible to both.
Now for Column widget needs list of widgets so how can we convert the list of
answers in a map into a list of widgets? This can be done by map() function
provided by List class (actually iterable class but as list extedns iterable so it also
has it). map() function allows to apply a function over each of the elements of list
on which it is called, replacing each element with a new one.
As map() takes a function as argument, So we can create an anonymous function and
pass it as argument to map().
The argument to the anonymous function, provided by framework, will be each item
of list on which map() is called. And the anonymous function should return the type
of item we want in the converted list. In our case we want a list of widgets so we
return Answer widget.
Now we still get an error because map doesn’t actually return a list but an iterable
(search what is it). But we can convert iterable into a list using toList() method.
But on which list we call the map() function? We call it on the list stored
corresponding to answer key in our list of Map’s. But it will still give error, why?
Because dart is not able to infer that value corresponding to ‘answer’ key, in map, is
actually a list. For this we need to tell dart that the value is defiantly a list and we do
this using Typecasting using ‘as’ operator (which is also known as .cast()).
NOTE - there is also another operator ‘is’ (which is known as .retype()). for type
casting but there is a difference between the above two (search more).
But if we do the above the Column widget will store the list of button widgets as a
list itself (i.e. there will be a nested list). But we need individual items of button
widget list. This can be done by spread operator (…) which tells dart to store the
items of (nested) list individually inside the (parent) list.
NOTE – This is not the full complete app. In the last 1-1.5 hours of the 2nd module (flutter
basics), the instructor provided score for each choice and displayed the personality based
on total score of choices and then provided how to reset quiz. But there was not much
new to learn thus you have not recorded it. Few new things learnt were -
Passing anonymous function’s pointer instead of address of function for call-back.
Getter (see if it comes in flutter or dart. If in dart add it in dart category)
EXPENSOR
This app calculates expenses of a user and shows it in a good-looking UI. So, we will learn
about a lot of build in Widgets for UI designing.
A Row is a widget used to display child widgets in a horizontal manner And Column in
vertical manner. The Row & Column widget don’t scroll.
Most Properties of Row and Column are given in here –
https://medium.com/jlouage/flutter-row-column-cheat-sheet-78c38d242041 or Offline &
Row Column Course Summary Offline
Container Widget
The Container widget is used to contain a child widget with the ability to apply styling and
aligning properties. For explanation on commonly used properties see the below doc -
https://medium.com/jlouage/container-de5b0d3ad184 or Offline
Container vs Columns/Rows
Both have their differences (but both are really important) Thus the most important thing is
use them in combination to harness their full potential. The differences are given below –
Card Widget
This is also a very important widget. It gives the card layout which we are mostly used in
modern UI’s. Properties explanation can be found in official docs -
https://api.flutter.dev/flutter/material/Card-class.html
In dart, like in other languages, we can concatenate strings using + sign. But if the we have
to concatenate a lot of stings then it becomes cumbersome (try converting DateTime.now()
into human readable string using + and you will know what I am talking about).
So, Dart provides alternate syntax using ‘$’ (Dollar sign). You can access the value of an
expression inside a string by using ${expression}. The example of the syntax is as
follows–
DateTime date = DateTime.now();
String format = "${date.day}/${date.month}/${date.year}";
The call to toString() is unnecessary (although harmless). In above case: toString() is
already defined for DateTime object and Dart automatically calls toString() .
Formatting Dates
For formatting date, either you can the date, month, or year format. But to get other
formats (like displaying month by name or getting last 2 digits of year), by minimal code, is
to use following packages from pub.dev –
a) DateFormat (Preferred – because of ease of use and documentation availability here
- https://pub.dev/documentation/date_format/latest/date_format/date_format-library.html )
b) Intl (Maybe this provides a more format but documentation is lacking. See it from
course - https://www.udemy.com/course/learn-flutter-dart-to-build-ios-android-apps/learn/lecture/15179244#overview ).
There are 2 methods to get input from a text field (Which we have used in input_card.dart)
–
b. Using onChanged – this property takes a function as its value and passes a string
value into that function denoting the updated/changed value of Text Field. This
can be used to store in a var and thus we will get the input.
NOTE - if you use this make sure that user is forced to change the text every time
before submitting (that is text field is cleared after accepting the input) so that you
don’t get the data can’t be null message. This is because if user presses button
without changing previous contents then a null value will be passed as argument
the onChanged function (because nothing changed. Previous state was intact).
This app also gives an important lesson on lifting the state up which is demonstrated by the
file transaction_ui.dart as explained below –
Lift the state up in a separate stateful widget (not in MyHomePage widget so that
MyhomePage can remain stateless because scaffold and app bar don’t need to change just
because a transaction got added. They will remain same and thus keeping MyHomePage
as stateless will boost the performance because build will not be called again and again to
build scaffold, materailapp, and app bar (in stateful build is called again as soon as state is
changed using setState()).
NOTE – this will be removed later (thus there is no file named as such in final app) when you
create Modal Bottom Sheet. This is because bottom sheet comes up when floating action
button is pressed which is in build of _HomePage. Thus, we have to define the function,
which displays bottom sheet, in the _HomePage. But bottom sheet uses InputCard which
takes addTransaction() method as parameter. So we also have to bring addTransaction()
to main.dart. But AddTransaction is the function that changes state and is part of the
State of transaction_ui.dart. Adding it to homepage means moving the whole state logic
of transaction_ui.dart to main.dart in which case transaction_ui.dart becomes redundant
as everything has to be shifted to main.dart.
Problem (pixels overflowed When keyboard comes up) - If the widget in which the
textfields are present is not scrollable then when we type input to text field using soft
keyboard, we will see a blackyellow line showing pixel overflowed. This is because when
keyboard comes up, the size of our viewport/screen size available (i.e. size of body in
scaffold) gets reduced (this you can see using flutter inspector and is also given here -
https://medium.com/@rubensdemelo/flutter-forms-improving-ui-ux-with-singlechildscrollview-
7b91aa981475). Now in a reduced viewport, the size of column will also reduce and in that
reduced column size (in fact in column size), if the widgets don’t fit, we will get this
overflowing error (By this error flutter tells us that there may be content you want to show
but flutter isn’t able to display it due to lack of availability of screen space. The pixels value
shows the screen space (in terms of pixels) needed to display all the widgets). Now to
overcome this problem we use some ScrollView object on the viewport (By wrapping the
viewport widget inside the ScrollView object, SingleChildScrollView is a ScrollView Object).
By adding scrollView object we say to flutter that it is ok for widgets to go out of
viewport/screen size. If some widget goes out of screen size you just hide it and when user
scrolls you show it and hide other widget on top according to the screen space you have.
(SEARCH MORE ABOUT THIS) Make sure that the parent widget of
SingleChildScrollView has a defined height so that it doesn’t get confused as
to what is the height on which it has to apply scrolling. This is done by
wrapping SingleChildScrollView in a container to which we can apply some
definite height. Or in our case, as part of scaffold body because it has a height
of the viewport. But if the parent container of SingleChildScrollView doesn’t
have height (like a column takes as much height as it can get which is
infinite hight. This is explained in course BUT SEARCH MORE ON
GOOGLE – why SingleChildScrollView/ListView don’t get applied
on a column but works on scaffold body property without any
parent). then it gets confused as to what is the height scrolling will be
applied to and thus you don’t get scrolling feature.
Apply SingleChildScrollView on the widget covering our whole viewport and
not its child widget which is being hidden by keyboard - In Expensor, instead
of applying scrollView on the scaffold body: column, which constitutes our
whole viewport, you tried to apply it on one of its child widget, Column
widget with list of transactions, as that was the part of UI which was being
hidden by keyboard (you did as above by wrapping column in
SingleChildScroll which in-turn was wrapped in a Container with height
property defined). But remember, the container (which wraps our
SingleChildScroll and transaction list column) will be part of our viewport
Column. Now with keyboard coming up, viewport Column will reduce but
because container will have constant height it won’t reduce and thus if size of
container exceeds the size of column you will again get the error. So, Apply
ScrollView on the widget covering our whole viewport and not its child
widget which is being hidden by keyboard.
The same concept of defining the height is applied on ListView also i.e. Make sure
the parent widget of ListView has a defined height so that it doesn’t get confused as
to what is the height on which it has to apply scrolling (eXplaination given in this
video).
This is done using keyboadtype: named parameter in textfield and InputTextType classes
static properties. Like the below shows how to get number keyboard –
keyboardType: TextInputType.number,
in iOS for double values this may not work because it may not give ‘.’ for entering double
value, in that case use –
keyboardType:TextInputType.numberWithOptions(decimal:true),
Use toSrtingAsFixed as shown below (it will round double to 1 decimal place) –
double price = 10.290;
String n = price.toStringAsFixed(1); //ans – 10.3
FloatingActionButton Widget
Used to add a floating action button. Some of the important named parameters you learnt –
i. Child: parameter – takes the widget which is to be displayed in the button. Generally
we use icons in this which we get by using Icon() widget. In Icon widget we use static
properties of ‘Icons’ class which are different icons you can use. An example of this
is –
FloatingActionButton(child: Icon(Icons.add));
ii. @required onPressed: - like any button, we have to give this a function callback. This
function is executed when button pressed.
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
This a button which just adds an icon but makes it work as a button. This is mostly used for
adding press feature to an icon in AppBar (Common in modern apps). Example of this is in
our main.dart file as shown below -
home: Scaffold(
appBar: AppBar(
title: Text('Expensor'),
actions: [
IconButton(icon: Icon(Icons.add), onPressed: () {}),
],
)
Modal Bottom sheet is a widget which comes up from bottom and makes the screen in the
background overshadowed. Important things to remember -
Programmatically – bottom sheet is shown using function
showModalBottomSheet(). This function takes 2 important arguments –
a. context: named parameter – this parameter takes the BuildContext object as
value in which MaterialApp is present as ancestor (more details about this in
problem 1 given below).
b. builder: named parameter – takes a function (through callback). The function,
whose pointer will be passed as value, should take a BuildContext object as
argument and should return the widget which will be displayed inside the
modal bottom sheet.
(will be explained in detail later) Close automatically when input is accepted - This
is done through navigation class object shown below line of code –
Navigator.of(context).pop();
Navigator.of() teels the topmost widget right now. And pop() pop’s off removes that
topmost widget so calling this in the submit function (which will be called on press)
will pop the modal bottom sheet.
Problem 1 - While using this you got this error - No MediaQuery widget found inside
showModalBottomSheet.
Solution - This was because the showModalBottomSheet tried to access the ancestor of
type MaterialApp from the given context (this tells about the widgets which are currently
present in widget tree and at which position they are). Now if you are using context of build()
function and in that function you have the MaterialApp then the context will not have
MaterialApp because before calling build MaterailApp was not in widget tree. Which did not
have any context of MaterialApp. So, there are 2 solutions –
a. Use Builder widget to get new context with MaterialApp present in widget tree as
ancestor. This is done by wrapping the Scaffold in Builder() Widget as shown below –
Widget build(BuildContext c) {
return MaterialApp(
home: Builder(
builder: (context) => Scaffold(),
)
);
)
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
void modalSheet(BuildContext ctx) {
showModalBottomSheet<void>(
context: ctx, builder: (dummyCtx) => null);
}
@override
Widget build(BuildContext c) {
return Scaffold(
appBar: AppBar(
title: Text('Expensor'),
actions: [
IconButton(
icon: Icon(Icons.add), onPressed: () => modalSheet(co
ntext)),
],
),
);
}
As you can see above, we have created a separate widget MyApp for MaterialApp
and int its home: called HomePage widget which contains the Scaffold. Now as
build in HomePage is called inside the MaterialApp thus its context will have
MaterialApp in widget tree as an ancestor.
Problem 2 - Text field gets cleared whenever focus is shifted to some other widget
Solution - Make the input card as stateful widget –
the problem with textfeild getting cleared in modal sheet is because the Input
card is a stateless widget and as soon as focus is lost, it returns back to its
original state i.e. - empty. You will study in detail more on state management
later in the course
Theming the app (providing custom & global colours, fonts, etc.)
Theme is set in MaterailApp widget using theme: named parameter which takes
ThemeData() object as value(ThemeData is not some widget but a Class).
ThemeData has a lot of named parameters but most important ones which you learned in
course are –
a. primarySwatch: - this takes a Color (using static property) and this will be applied to
the appbar and can be used as value to any widget using color: named parameters
where you want to use the theme colour as follows –
color: Theme.of(context).primaryColor
b. accentColor: - This takes the secondary color of the theme. This will be implicitly
applied to elements like button. And can be applied manually like above just using
accentColor instead of primaryColor.
c. fontFamily: - this lets you define font that will be used for theme. For custom fonts
you need to add the fonts to pubspec.yamal file (method is given in the comments
of yamal file itself. Just remember that for bold you have to define the weight the
bold font takes which is give in official docs). Now after adding the font use the font
in MaterialApp as follows (Make sure after adding to the files you restart the app) –
MaterialApp(theme: ThemeData(fontFamily: 'Quicksand')
d. appBarTheme – let us suppose that you want to set a particular Theme for all
AppBars in the in the app then we pass AppBarTheme() (again a class not a widget).
More detail on this is given in next section.
Now as you can use the value of color in palace of any color: named parameter
similarly you can use this value in place of other style: named parameters Also
shown in next sec.
e. textTheme – this can be used to set themes for the whole app globally. This is
explained in detail in next to next section.
Advantage – the main advantage is that of using a hard-coated number vs macro constant
i.e. you don’t need to change it everywhere if you decide to once you change it at one place
in MaterialApp theme it will automatically get applied to all other.
Setting Custom Text for All App-Bar’s in Our App Using AppBarTheme class
MaterialApp(
theme: ThemeData(
appBarTheme: AppBarTheme(
textTheme: ThemeData().primaryTextTheme.copyWith(
headline6: TextStyle(fontFamily: 'OpenSans'),),),),),
NOTE – value for textTheme can also be replaced by the following line –
TextTheme().copyWith(
headline6: TextStyle(
Now you can use this style of headline6 in place of other Styles where you need the
exact same style as follows –
style: Theme.of(context).textTheme.headline6,
Setting Same text Theme in MaterialApp Globally (For code reusability & maintainance)
This can be using textTheme: named parameter of the ThemeData() Class. It is very similar
to above just replace headline6 with bodyText2.
bodyText2 is the text style used for widgets which don’t define their own text style. So,
use this for the most used text style in the app so that code is reused the most. The code
for this is given below –
MaterialApp(
theme: ThemeData(
textTheme: TextTheme().copyWith(
bodyText2: TextStyle(
fontFamily: 'Quicksand',
fontSize: 15,
fontWeight: FontWeight.w500,
color: Colors.grey[700],
),
),
NOTE – As stated above, if you want to use bodyText2 as style for some text, you don’t
need to provide any value to style: parameter, that is the default style flutter assigns to a
text if no style: value is provided.
You can use the bodyText1: parameter also, but flutter will not implicitly assign it, instead
we will have to use it in the style: named parameter of the text widget. Just replace
headline6 with bodyText2 in this syntax (above).
Sizedbox() Widget
Sometimes we just need to provide some space in-between for which we can use container
but there is another widget which specializes in it i.e. SizedBox() because it has few
paramters as compared to container and is mainly used for spacing only.
It does take a child widget but is optional so it can be used to just displays empty space
and it has a height and width attribute which can provide the amount space we would
like. The syntax of it as follows –
List.generate() – Returns a list with initialized values (done by the generator function
passed in it which decides the values to be generated).
More formerly - Creates a list with [length] positions (1st argument) and fills it with
values created by calling [generator] (2 nd arg) for each index in the range 0 … length -
1 in increasing order. Ex -
List.generate(7, (i) => i);
The above will generate a list of 7 values in which each element will hold its position
inside the list as its value. (For this you would have needed a loop code).
iterable.fold(0, (sum, element) => sum + element);
<List_Name>.where() – This returns a new lazy [Iterable] with all elements that
satisfy the predicate [test] (1st and only argument). This predicate has to be a
function call-back which returns a Boolean value to indicate if this element (given in
argument) has to included in the new list or not. Given example returns a list which
includes dates which are after certain date – using where() [_transactions is a list of
objects. The object has a date property] –
_transactions.where((element) =>
element.date.isAfter(DateTime.now().subtract(Duration(days: 7))))
.toList();
Stack() Widget
This widgets stacks one widget on top of another widget with the first widget in the list of
widget (specified using - children: []) being the bottommost widget.
FractionallySizedBox() Widget
This gives a box whose height and width are fraction of the height of its parent. This igiven
by specifying two named prameters – heightFactor and widthFactor.
Now as the height and width will be fractions of parent height the value of each factor
should be between 0 and 1 (0 being height equal to its parent and 1 being no height).
Note – This function only provides height and width factors. There is no parameter for
height or width of the container itself i.e. it will be totally based on parent height. As such
the parent container must have some height and width defined or else this will cause
error.
Flexible() Widget (GO DEEP IN IT STILL YOU KNOW ABOUT THIS THEORATICALLY ONLY)
(NOTE - THIS WIDGET CAN BE VERY TRICKY SO YOU NEED TO PLAY WITH THE WIDGETS
AND THEIR PARAMTERS TO DEEPLY UNDERSTAND WHAT YOU DO. GO DEEP IN THIS)
Now this widget forces a child widget inside a Column() or Row() to take up as much space
as much is provided to it using flex: attribute. To use this wrap each child of column() or
row() inside a Flexible() widget.
Two main named parameters you learned about are –
1. flex: – as stated above tells the amount to be take by the widget wrapped inside
Flexible(). This actually defines the ratio in which the space will be devided amost
the children wrapped with Flexible.
If flex: is not used then implicitly all the containers are provided value as 1 which
means they must take up same space
2. fit: - How a flexible child is inscribed into the available space.
If flex is non-zero, the [fit] determines whether the child fills the space the parent
makes available during layout. If the fit is [FlexFit.tight], the child is required to fill
the available space. If the fit is [FlexFit.loose], the child can be at most as large as
the available space (but is allowed to be smaller if the size of the child’s contents
are smaller).
Expanded() is just Flexible() with fit: set to FlexFit.tight and thus you don’t have a fit:
parameter defined for this widget.
FittedBox() Widget
This Creates a widget that scales (so it will shrink or expand the child to take the full space
of the box) and positions its child within itself so that the box takes as much space as it is
allowed to take.
The way in which the content can be scaled to fit the box is provided using fit: named
parameter. The default value is BoxFit.contain if not provided. You can see what it means
and other values that can be provided with their meaning using the IDE.
Chart in Expensor
Now we will build the chart which shows what part is of the total spending is the spending
on a particular day. This can be done using simple widgets already taught but you can
download external packages which can be used to build more complex charts. The main
steps -
The container that will hold all the bars will be a card.
The card will have 7 bars, one for each weekday. Each Bar will have 3 components –
a. The expenditure on the particular day
b. A cylinder (test tubeish) to show the percent the above expenditure is of
the total expenditure in the last 7 days – the method to make it is described
-
The main logic for creating the cylinder showing the expenditure
percent is that we will use Boxes.
First one will be of a constant size depicting the cylinder itself – this can
be done easily by just using a container and giving it some colour and/or
border using BoxDecoration().
On top of above will be the 2 nd box which will be coloured according to
the percent/fraction of the total expenditure on a particular day. This
cn be done in two way – either by using another container whose height
will be the fraction of height of the parent container or using
FraSizedBox().
c. Initials of the week day the above day is related to
We will be using the following new widgets for above task (all of which are described in
detail above) –
ListTile() Widget
This is a real code saving and useful Widget. Whenever we use ListView Builder mostly a list
element will be an element which will have a leading element then some title giving info
about the element and a subtitle giving additional info about element and at the end some
additional info about the element. Example of the ListTile used in our project is –
Leading: - leading show the object which will come in the ListTile element in the
above case the Circle with price
Title: - the info about what the transaction is given using this parameter. Like we
bought Gita in this transaction.
Subtitle: - additional info is that we have bought it on 31 st July and as it is additional
info, it is in shorter and greyer text.
Trailing: - gives the widget which will be shown at the end and this is usually a delete
icon but it can be any widget.
Just like to show modal sheet there is a method, to show a date picker also there is a
method which is named showDatePicker(). Below is the description from docs with red
coloured text your own explanation.
CircleAvatar() Widget
This creates a circle like container (obviously the features of this are really less when
compared with container but container can’t be circular. You can give container a circular
border but it will still be square in reality just the appearance changes externally. You can
test this by fitting a text inside that container. Now wrap the text with Fitted box and you
will see the text will go outside the border because the container is still square in reality just
the appearance of it changes and thus for the fitted box also the container is square and it
will try to fit the contents in the square box not in the circle seen on screen).
Typically used with a user's profile image, or, in the absence of such an image, the user's
initials. To use this is easy – provide the widget you want to show inside the circle in its
child: parameter. Additionally, you can change the radius by radius: and provide a colour
also.
WEB)–
YOUR TRIED ATTEMPT TO EXPLAIN THE CONCEPT USING COURSE DEFINATION BUT THIS IS
MAY BE WRONG AS COURSE ITSLEF DOSEN’T EXPLAIN IT WELL
Immutable objects are those objects whose instance variables values can’t be changed in
any possible way. Now this is done by making –
a. the constructor of the class as const along with
b. making every property as final and
c. The object itself should be const.
[Note – We have to meet all the given conditions then only we can create a truly immutable
object –
If condition a. is not met then then compiler won’t know that this is an immutable object
eve thought its values are final and thus will call the constructor again
If condition b. is not met then we can change the values using a member method so it is not
truly immutable
If condition c. not met then we can provide the object a new value by calling the constructor
again with different value which will create a new object and the values of the instance
variable will change]
The advantage and use of immutable objects/compile time objects are that flutter can
simply use the object that is first assigned and use it every time build is called. A new
object won’t be created as flutter knows that the object itself is constant and its value
can’t be changed in any possible way with each new build() call.
*
NOTE – The variable should also be const/final or else the variable can itself be provided a
new object and thus flutter will have to create a new object. To understand look at below
example –
Ways to improve code (In Section 6 – Widget and flutter internal deep dive) –
(readability) To separate each widget code into different files (video 143).
(readability) using builder methods to separate iOS specific code from Android code
(Video 144).
(performance) using const objects via const constructors to create immutable
objects (video 138 – 141) To understand more on why do this look here -
https://flutter.dev/docs/resources/inside-flutter
https://www.youtube.com/watch?v=wE7khGHVkYY
https://www.youtube.com/watch?v=AqCMFXEmf3w
Context object holds the data about every widget so that when we need data of some
widget, we get without passing a reference in the constructor.
https://www.youtube.com/watch?v=Zbm3hjPjQMk
GridView.builder()
This like list view builder builds a grid view (a view with square brackets) and only loads
those elements which are visible on the screen.
GridTile()
It is a built-in widget which allows us to build a grid element in GridView. This has named
parameters –
a) child: - to provide the widget [mostly an image] which will be covering the whole
GridTile
b) footer: - to provide info at the bottom of the tile about the child widget
c) header: - to provide info at the top of the GirdTile about the child widget.
This widget builds a bar which is usually accompanied in a Grid element to display info
regarding the child widget contained in the Grid Element. It is similar to ListTile as it also
has leading, trailing, title, and subtitle named paramters.
ClipRRect()
This stands for clip Rounded Rectangle and thus this clips a rectangle in a rounded shape to
give it a rounded effect. Its 2 main named arguments are –
a) child: - defines the child widget whose border will be clipped in circular shape.
b) borderRadius: – defined how much rounded border curves you want for the widget
given above.
GestureDetector()
Creates a widget that detects gestures. The child widget (provided thorugh the
child:named paramters) is actually the widget which will listen for the gestures.
There are different gestrues (like onTap, onLongPress etc) described in for of named
parameters.
Tasks that will be done as soon as a gesture is detected by the child widget – is defined by
the function which is passed as value to the named argument of that gesture.
below is a code snippet to demonstrate the use of this widget. The below code will print 10
in the console as many times the user taps in the image –
GestureDetector(
onTap: () => print('10')
child: GridTile(
child: Image.network(
imageUrl,
fit: BoxFit.cover,
));
Navigator
A widget that manages a set of child widgets (pages/screens) with a stack discipline.
Mobile apps typically reveal their contents via full-screen elements called "screens" or
"pages". In Flutter these elements are called routes and they're managed by a [Navigator]
widget. The navigator manages a stack of [Route] objects and provides two ways for
managing the stack, the declarative API [Navigator.pages] (not taught thus far in course) or
imperative API [Navigator.push] and [Navigator.pop].
Imperative API -
Imperative means which is given implicitly/automatically and declarative means those for
which has to be defined explicitly.
Now even though we can create declarative API it's most common to use the imperative
navigator created by the [Router] which itself is created and configured by a [WidgetsApp]
or a [MaterialApp] widget. You can refer to that navigator with [Navigator.of].
A [MaterialApp] is the simplest way to set things up. The [MaterialApp]'s home becomes the
route at the bottom of the [Navigator]'s stack. It is what you see when the app is launched.
void main() {
runApp(MaterialApp(home: MyAppHome()));
}
The route defines its widget with a builder function instead of a child widget because it will
be built and rebuilt in different contexts depending on when it's pushed and popped.
As you can see, the new route can be popped, revealing the app's home page, with the
Navigator's pop method:
Navigator.pop(context);
Mobile apps often manage a large number of routes and it's often easiest to refer to them
by name. Route names, by convention, use a path-like structure (for example, '/a/b/c'). The
app's home page route is named '/' by default.
The [MaterialApp] can be created with a [Map<String, WidgetBuilder>] which maps from a
route's name to a builder function that will create it. The [MaterialApp] uses this map to
create a value for its navigator's [onGenerateRoute] callback.
Navigator.pushNamed(context, '/b');
NOTE – Instead of hard quoting the name of the route in a string and use it at different
places, we can create a static variable in the widget which creates the route/screen (main
Class in the <screen_name>.dart file) and use that everywhere so that if we change name
of route we have to do it at just one place. (The example of this can be seen in SHOP app
main.dart, product_items.dart(given below), product_detail_screen.dart(given below)
[video 188 in course]).
class ProductItem extends StatelessWidget {
final String name = 'nikhil';
final String id = 'p1';
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.of(context).pushNamed(
ProductDetailScreen.routeName,
arguments: {'name': name, 'id': id}),//MAIN LINE - PASSED ARGUMENT
child: Text('Tap to pass arguments'),
);
}
}
Now the screen in which we want to access the arguments we can access it through the
following code line (whole code of the screen is given below the line) –
class ProductDetailScreen extends StatelessWidget {
static const routeName = '/product-detail';
@override
Widget build(BuildContext context) {
//MAIN LINE – TO ACCESS THE ARGUMENTS using map variable
final map =
(ModalRoute.of(context).settings.arguments as Map<String, Object>);
final name = map['name'];
final id = map['id'];
return Scaffold(
appBar: AppBar(
title: Text('$name($id)'),
),
);
}
}
State Management (Using Provider package) [videos 189 – 191 & 193 - 196]
We need states (data members which effect the UI and which might change over time.
App-state is a state which effects the entire app or large parts of the app [like
authentication status of user. It is needed across entire app] whereas Local/Widget state is
a state which effects that widget itself. We need state management (using provider
package) for app state. Local/widget state can be managed through Stateful widgets using
setState()) in different widget. There are 2 methods to manage app-state -
1. Through Constructor - first way is to pass the state (app state to be specific) through
the constructors to all the widgets that need the state. But it has the following
disadvantages -
i. Becomes cumbersome in larger apps because it may happen that we are just
passing data which is not required by a widget A but just because widget A is
in turn calling widget B which needs that data, we need to pass it through the
constructor of Widget A.
ii. Impacts performance if our states are in a central file like main.dart. Because
if any change happens to even one of the widgets in main.dart file the whole
widget tree is rebuilt (that many a times means the whole app because the
routes/screens are also present in the main.dart and thus they are also
rebuild even though they have no connection with the change and don’t
need to be rebuild) even though we don’t need it.
2. Using Provider Package - The other and the preferred way is to use the provider
package (It is an external package to be installed through the pubspec.yaml) which
provides a data container (or sometimes called data provider. This data container is
nothing but an object of Provider Class) which can be attached to any widget in our
app (in the below ex it is MyApp but it can be any widget).
Now all the children of MyApp can listen to changes in this container by using a
listener to this container and thus access and react to changes to the data in the
container (this data is our app-state. So, in this section data = app-state).
Now because it is a listener, whenever the data (app-state) in the container
changes the widgets which are listening to it, their build() is called. And now this
does not only solve the problem of passing the data through constructor but also
overcomes the performance problem because only those widgets will be rebuilt
which are listening to the container and not the other children (which are not
listening to the container). Like in the below only SingleProduct will be rebuild and
others – Products, ProductDetail, and Cart will not be rebuilt.
You saw the theory about Provider package in previous section. Now the actual
implementational details are provided here –
a. Install provider – Firstly we have to install the provider package which is a 3rd party
package and thus its specifications are to be provided in the pubspec.yamal file.
Now the provider package gives us the ability to notify listeners (of the instance of
provider class) about the changes that happen to the data inside the instance. But
how? Notifying listeners about changes is done via the notifyListeners() methods
which is provided in the ChangeNotifier Class.
Now whatever change we do to the data, after the change, we have to call the
notifyListeners() method so that flutter can tell all the listeners of our provider class
instance that the data has changed.
Note – This is like the setState() - so any state (data) changed without calling
notifyListeners() will not result in any change (rebuild) to the listeners as they won’t
be notified by flutter that the state (data) is changed. Flutter notifies listeners only
via notifyListeners() and thus be careful that the state can’t be changed from
outside the provider class. This is done by making the state private and also when
retuning values through getters - return the values by copying it in a new variable
and not directly returning the state member as doing it will return the reference of
the state and thus any change outside will also change the value of state that too
without notifyListeners(). This is why in the app code we are returning […_items] as
this is copying the values of _items into a new list and that list is being returned and
not the reference to _items.
So, we have to use ChangeNotifier as a mixin to the class in which we keep our
state so that we can use notifyListeners() inside our class to change the state. The
example of the code is given below -
Now the class(es) which can listen to a provider object are children of the class to
which we have attached this provider object (explained in the above section).
So, we attach this provider object to the MyApp Class because we need the list of
products (our state stored in the provider) in the ProductsOverviewScreen and
ProductsDetailScreen both of which are children of the MyApp.
So, to attach provider object to MyAPP, we wrap the MyApp class with the
ChangeNotifierProvider which is provided to us by the provider package so first
import the package in the file. This is shown below -
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (ctx) => Products(),
child: MaterialApp(
title: 'MyShop',
Now the above allows us to attach an instance of our provider class, to MyApp, using
create: named argument which takes a function. The Function takes a BuildContext
as argument and returns an instance of the provider class (in our case the Products
class). Changes to this returned instance will be listened by child widgets (NOTE –
Changes to only this instance (returned from inside the ChangeNotifierListener)
will be listened. Changes that happen in any other instance of the provider class
are not listened. This is explained in detail with code here).
Now the child widgets of MyApp can listen and whenever the data in the provider
instance (i.e. our state) changes, the child widgets (which are listening) are rebuild.
Note – when you have to create a new instance of the provider class use the above
approach (i.e. ChangeNotifierProvider with create: named argument) but in some
cases, like when working with lists or girds, you may use an instance of provider
class which is already instantiated and thus not create a new object. In that case,
use the ChangeNotiferProvider.value() constructor and in it provide the already
created instance to value: named parameter. This is shown in the
products_grid.dart file or video 196 in course. A code snippet is shown below –
GridView.builder(
padding: const EdgeInsets.all(10),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10)
,
itemCount: products.length,
itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
value: products[i], //not a new object
child: ProductItem(),
),
Method 1 – provider.of<>()
we need to create a communication channel between the provider instance we set
up in the ChangeNotiferProvider and the child widget which needs the data and
wants to become a listener. This we do using the following line of code (given in
product_grid.dart file) in the child widget (which wants to listen) –
final productsOB = Provider.of<Products>(context);
.of() is used to set up a direct communication channel and can be only used if the
widget in which we are using this .of() is direct or indirect child of a widget which
sets up the Provider Class on which we are calling this .of() method.
Note – The widget that is going to call Provider.of() “needs” to be a direct or indirect
child of a class which is already a child of ChangeNotifyProvider class [it inherits
Provider class]. In the above case also, we use this code line in
ProductsOverviewScreen which is child of MyApp which is a child of
ChangeNotifyProvider (This inherits Provider class and thus we can use
Provider.of()).
As we can have multiple provider objects (Each obj will be from a different provider
class), we need to tell to which provider object we want to set up the connection.
This we do by specifying the provider class name (Products in our case), to whose
object we want to connect, inside the angular brackets as follows –
.of<Products>(context)
By the above line, as we use Products in angular braces, we tell flutter to connect to
the object which is of the Products provider class.
class ProductsGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<Products>(builder: (context, productsOB, child) {
final products = productsOB.getProducts;
return GridView.builder(
//Code to display individual grid items
padding: const EdgeInsets.all(10),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,crossAxisSpacing: 10,mainAxisSpacing: 10)
,
itemCount: products.length,
itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
value: products[i],
child: ProductItem(),
),
);
});
}
}
As you can see the GridView is wrapped in Consumer<> widget (the function,
provided to builder, returns the GridView widget) so that if any product gets added
in the list of products, they are shown in the GridView also. Also note that the
function takes 3 arguments –
a. ctx - BuildContext (as usual),
b. productsOB - Instance of the provider class to which we will listen,
c. child – this parameter provides that part of the widget which will remain
constant and doesn’t need to be rebuild. The part which will not rebuild is
provided inside the Consumer object through child: named parameter
(Example of this can be seen at 5:37 in video 197).
To access the members inside the Provider Container - There are 2 ways to access data
in Provider Class –
a. Using Provider.of() - Now the above line of code using Provider.of() will not only
make the child class a listener but loadedProducts will also become an object of
the Products class which can be used like normal objects to call the non-private
methods or properties inside the Products provider class.
NOTE – we can also use Provider.of() simply to create an object and not to set up a
listener by using listen: named argument which takes a bool value. Default is set to
true (that is why the Provider.of() by default makes the child class a listener) but if
we set it to false then only an object will be created and the widget (in which we
use Provider.of()) won’t be registered as a listener i.e. the widget won’t be rebuild
even if any data changes in the provider container class. The example is shown
below -
final productsOB = Provider.of<Products>(context, listen: false);
Now this will make productOB only an object and not a signal to create the widget a
listener and thus using this line will not make a rebuild even if the data inside
Products changes.
b. Using the Instance of the Provider Class – now as the provider container is a class,
we can create an instance of the class by simply importing the file in which the class
is stored and create its object, as we normally do, to access data members or
methods inside the class. The only downside is that it will not make the widget a
listener for that we will have to use Provider.of().
Changes to only the instance attached using the ChangeNotifierProvider are listened –
Typically, when working with the Provider package, you provide objects based on your
own custom classes.
This makes sense because you can implement the ChangeNotifier mixin in your classes to
then trigger notifyListeners() whenever you want to update all places in your app that
listen to your data.
But you're not limited to providing objects - you can provide ANY kind of value (lists,
numbers, strings, objects without ChangeNotifier mixing, ...).
Example:
You might wonder, how this text can change though - it's a constant text after all. It
certainly doesn't implement the ChangeNotifier mixin (the String class, which is built-into
Dart, indeed doesn't - just like numbers, booleans etc.).
It's important to note, that the above snippet uses Provider , NOT ChangeNotifierProvider .
The latter indeed only works with objects based on classes that use the ChangeNotifier
mixin. And this is the most common use-case, because you typically want to be your
global data changeable (and have the app UI react to that).
But in case you just want to provide some global (constant) value which you can then
conveniently use like this:
PopupMenuButton
PopupMenuItem
Defining Enum