Some Flutter Notes
Notes from my learning path to understand flutter
Hi everyone, so, these are like personal notes about fundamental things I've been learning about flutter & dart.
Don't take this post to serious, I don't expect this blog post to be on page #1 on google, lol, but if you see something wrong on it, I'll appreciate it if you let me know.
Create Project
flutter create my_cool_app
Run Project
flutter run
// On VSC
f5 or debug/start debugging
ShortCodes
stless // Creates a State less widget
stfull // Creates a State full widget
Tip: On VSC, hover the item and see the yellow light, you can see options to do things faster like wrapping images on center widget and so on.
Widgets
Container()
- It is as big as possible, it takes all the space
- Children will be just its own size, it won't take all the width of Container() at least we set it to.
- You can have only one child
Row(), Column()
- Multiple children with Children()
- Row() stretch horizontally, Column() stretch vertically
SafeArea()
- Created widget that ovoids operating system interfaces
Text()
- Simple text widget
Expanded()
- It has to go under Column, Row or Flex widgets, it takes all the width or height of widget, so child don't go outside its parent, Column, Row etc.. it behaves like display flex on development, so, if there two Expanded widgets under a Row, the will have a width of 50%, you can also set a property of flex onside it that behaves kinda the same way as the CSS attribute.
FlatButton()
FlatButton()
& RaisedButton()
-> use onPressed:(){ print("btn was pressed") }
TextField()
TextField()
works as an input on web, -> use onChanged: (value){ print(value); }
to get value on change.
Select/Dropdown
The DropdownButton()
will create a dropdown, value
needs to be populated so it doesn't show and empty box and also needs to be updated with onChanged
so it gets updated when user change option, also its items
needs to be populated with DropdownMenuItem
.
You can set it to be a DropdownButton<String>
with type of string, or you can leave it alone to make it dynamic.
Toast
Creating a toast widget with a MaterialApp
is as easy as:
Scaffold.of(context).showSnackBar(SnackBar(
content: Text("My Cool Material Toast"),
));
...
String selectedItem;
...
DropdownButton<String>(
value: selectedItem, // this is the default value for the Select, without it, it will show an empty box
items: [
DropdownMenuItem(
child: Text('My Item 1'),
value: 'Value 1',
),
DropdownMenuItem(
child: Text('My Item 2'),
value: 'Value 2',
),
DropdownMenuItem(
child: Text('My Item 3'),
value: 'Value 3',
),
],
onChanged: (value) {
setState(() {
selectedCurrency = value;
});
},
),
GestureDetector()
Detect gestures, you can use something like onTab:(){ print("tab") }
Slider()
You can customize the material flutter widgets with the ThemeData
class, so, for example to customize the slider even further, you can do somthing like:
SliderTheme(
data: SliderTheme.of(context).copyWith( // this to inherith default SliderTheme styles and don't have to write all the theme data from scratch
thumbColor: Colors.red,
...
),
child: Slider(
value: 110,
...
)
)
Creating a circular button
You can create a new custom widget, then extend the RawMaterialButton, and make the sape circular:
class RoundIconButton extends StatelessWidget {
RoundIconButton({this.icon, this.onPressed});
final IconData icon; // to get the icon content
final Function onPressed; // to get the function to run on onPressed
@override
Widget build(BuildContext context) {
return RawMaterialButton(
onPressed: onPressed,
child: Icon(icon),
elevation: 6.0, // Shadow
constraints: BoxConstraints.tightFor( // Size of the icon
width: 56.0,
height: 56.0,
),
shape: CircleBorder(), // make it circular
fillColor: Color(0xFF4C4F5E), // Background of the button
);
}
}
Variables and functions
var myVariable;
void myFunction() {}
// With Arguments/Input
void myFunction(int myArgument) {
print("my argument integral is $myArgument");
}
// Functions that allows to send arguments in different
// order than the ones that were in the function itself
greet(greeting: 'How do you do', personToGreet: 'Jonny');
void greet({String personToGreet, String greeting}){
print('$greeting $personToGreet'); // How do you do Jonny
}
// `void` functions are functions with not output, if we need a function with output,
// we have to set it to the data type we are going to return, for example:
// int myIntFunction(){ return intVar; }
// String myStringFuction(){ return stringVar; }
// Widget myWidgetFunction(){ return myWidget(); }
// Expanded myExpandedWidgetFunction() { return Expanded(Text("Hi")); }
int myIntFunction(int money){
return money - 2;;
}
int result = myIntFunction(3);
print(result); // 1
// Arrow Functions
myArrowFuction(int num1, int num2) => num1 + num2;
Data types
Dart is a Statically typed language
, it means you CAN NOT change a variable with values of String to be a number or boolean later if the variable was already declared with a defined data type
// Primitive types
String "MyVar"
int 1234
double 10.2
boolean true / false
List [ ]
// ----
var a;
a = "hello"
a = 1234
print(a) // 1234
// you can also set a variable like this
String stringVar = "Hello";
int numberVar = 12345;
dynamic dynamicVar; // it can be any data type
String interpolation
use $var
in the string, also, for situation were you need the interpolation of something like myclass.myProperty
, you need to use ${myclass.myProperty}
print("my string with my $variableToBeInterpolated");
print("my other string interpolation ${myclass.myProperty}");
For loop
for(int i = 0; i<5; i++){
// Print i, 5 times
}
For in loop
List<String> fruits = [
'apple',
'pear',
'orange',
'grape',
'banana'
];
for (String fruit in fruits){
print(fruit); // apple -> pear -> ... banana
}
DartPad
Use DartPad to run Dart codes on browser to test and so on.
StateLess Widget
Immutable (unchangable) widget, that doesn't change
StateFull Widgets
Use setState(() { //Do Something})
to change the state of the widget, it will check for dirty
elements in the widget and update the build of the widget
Flutter Packages
Check https://pub.dev/flutter/packages for finding flutter packages, later to implement it:
on pubspec.yaml
//under
dependencies:
my_new_package: ^1.0.0
just saving the file, Visual Studio Code would installed for you, if you have any issue, use flutter pub get
to install it.
later on your dart file, you just need to import it:
import "package:my_new_package/my_new_package.dart"
// And use it as you wish
myNewPackage.dothis()
Adding Assets and Fonts
edit file pubspec.yaml
// For setting the images folder,
// you can set individual files too
assets:
- images/
// For Fonts with variations
fonts:
- family: Pacifico
fonts:
- asset: fonts/Pacifico-Regular.ttf
// Regular and Italic
- family: Source Sans Pro
fonts:
- asset: fonts/SourceSansPro-Regular.ttf
- asset: fonts/SourceSansPro-Italic.ttf
style: italic
// Regular and Bold
- family: Trajan Pro
fonts:
- asset: fonts/TrajanPro.ttf
- asset: fonts/TrajanPro_Bold.ttf
weight: 700
When importing a library
if you are importing a library with a lof of classes and you want to import only one, you can use show
like this:
import 'dart:io' show Platform; // only import Platform class from dart:io, then you can use Platform.isIOS
you can use hide
to show all classes in that package but the one hided:
import 'dart:io' hide ProcessInfo; // show all clases but hide ProcessInfo from dart:io
also, we can use as
that will rename the entery package to the name given
import 'dart:io' as myPackage; // now you can reference it as myPackage.MyClass()
Working with lists
Note: Keep in mind that if you are going to add items to an empty list, you have to initialice it with an []
value, like this: List<Text> myTextList = [];
; if you leave the list as null: List<Text> myTextList;
it may cause bugs when trying to use the .add()
method in that list.
// Declare List
List<Widget> myList = [
myWidget("Widget 1"),
myWidget("Widget 2"),
],
// Dynamic List Type:
List myList = [ "a", 1]
// String List Type:
List<String> myStringList = ["a", "b", "c"]
// Using List
Column(
Children: myList,
),
// Accessing one item
print( myStringList[2]); // c
// Finding item
myStringList.indexOf("a") // 0
// Adding things to the list: List.add( ), it will add item in the last position
FlatButton(
onPressed: () {
myList.add(
myWidget("Widget 3"),
)
},
),
// Insert item in any position, insert(index, element)
myStringList.insert(1, "d") // "a", "d", "b", "c"
// Get first item and last item
myStringList.first
myStringList.last
Building a class
This is the class, you can create a question.dart
file, remember classes always start with uppercase
class Question {
//Properties
String questionText;
bool questionAnswer;
// Constructor
Question({String q, bool a}) {
questionText = q;
questionAnswer = a;
}
// Method
void changeQuestion(){
}
}
using the class
Question q1 = Question(q: 'my question', a: false);
printing the new object created with the class
print(q1.questionText) // my question
print(q1.questionAnswer) // false
Creating a list (array) of new Questions List<Question>
List <Question> questionBank = [
Question((q: "Question 1"), (a: false)),
Question((q: "Question 2"), (a: true)),
Question((q: 'Question 3 with an hi\'s message'), (a: true)),
]
// Accessing it
questionBank[1].questionText // Question 2
More class examples
Creating the class
class Car {
// Properties
int numberOfDoors = 5;
// Methods
void drive (){
print("start running car");
}
}
Creating an object from the class
Car myCar = Car();
Human class example
class Human{
// Properties
double height;
int age = 0;
// Constructor
Human({double startingHeight}){
height = startingHeight;
}
// Method
void talk(String whatToSay){
print(whatToSay);
}
}
// Using the human class
Human jonny = Human(startingHeight: 15);
print(jonny.height); // 15
jonny.talk("Hi There") // Hi There
// Change the value of a property
jonny.age = 11; // This happens because the class is not encapsulated and its properties are not private
// NOTE - If you are going to use the class only one time for example, you can import it an use it like this:
Human(...).talk();
Object orientated programming
The 4 pilates for (OOP)
- Abstraction
- Make things modular (no run everything in just one function, method:
spaghetti code
)
- Make things modular (no run everything in just one function, method:
- Encapsulation
- Inheritance
- Polymorphism
Encapsulating a class
class Question{
// Add an _ before the property name to make it private and only accessible inside the class scope
List <Question> _questionBank = [
Question((q: "Question 1"), (a: false)),
Question((q: "Question 2"), (a: true)),
Question((q: 'Question 3 with an hi\'s message'), (a: true)),
]
// Make a getter that will return the question text needed
String getQuestionText(int questionNumber) {
return _questionBank[questionNumber].questionText;
}
}
// Getting the question text in a different class
Question questionList = Question();
String questionTest = questionList.getQuestionText(1); // Question 2
Inheritance a class
Use extends
to inherit another class
class Car {
int numberOfSeat = 5;
void drive() {
print('wheels turn.');
}
}
class ElectricCar extends Car {
int batteryLevel = 100;
void recharge() {
batteryLevel = 100;
}
}
// Using Electric car
ElectricCar myTesla = ElectricCar();
myTesla.drive(); // wheels turn. // you can see even drive() is not on ElectricCar,
// you can access it because it was extended from Car
myTesla.recharge()// you can access the methods defined on ElectricCar as usual.
Polymorphism in a class
Basically to override or improve methods from inherited (extends
) classes using: @override
& super.method()
class Car {
int numberOfSeat = 5;
void drive() {
print('wheels turn.');
}
}
// override
class LevitatingCar extends Car{
@override
void drive() {
print("glide forwards");
}
}
// override and super
class SelfDrivingCar extends Car {
String destination;
SelfDrivingCar(String userDestination) {
destination = userDestination;
}
@override
void drive() {
super.drive() // trigger parent behavior. Because we override it, we called it again to be used in our method
print("steering towards $destination"); // improve code
}
}
// Using them
LevitatingCar myMagLev = LevitatingCar();
myMagLev.drive() // glide forwards -> see how here, even though we extended Car, we don't get `wheels turn`
// because we override the drive() method to do wherever new thing we wanted to make it do.
SelfDrivingCar myWaymo = SelfDrivingCar("Medellin, Colombia");
myWaymo.drive() // (wheels turn.) and (steering towards Medellin, Colombia) -> here, we override it but we call
// the super.drive() to run parent drive code again and we attached our own code too (steering towards...)
Constructors on Dart
class Human {
double height;
// Constructor
Human({double startingHeight}){
height = startingHeight;
}
}
Human jonny = Human(startingHeight: 20);
print(jonny.height); // 20
Using this
:
class Human {
double height;
// Constructor
Human({double height}){
height = height; // will give null
this.height = height; // with `this`, it will be access the `double height variable` set before,
// without it, system will thing we are using the same height set as property in the constructor
}
}
Human jonny = Human(height: 20);
print(jonny.height); // 20
Short hand and convenient way of setting multiples properties in a constructor
class Human {
double height;
int weight;
String name;
// Constructor
Human({this.height, this.weight, this.string}){ }
}
Human jonny = Human(height: 20, weight: 4, name: Jonny);
print(jonny.height, jonny.weight, jonny.name); // 20, 4, Jonny
Themes
On an material app
MaterialApp(
theme: ThemeData.dark(), // default one is ThemeData.light()
);
// Custom properties
MaterialApp(
theme: ThemeData(
primaryColor: Color(0xFFFF0000), // for using HEX colors without alpha in the class Color(),
// just use 0xFF before the HEX color and that's it
accentColor: Colors.purple,
scaffoldBackgroundColor: Colors.blue,
textTheme: TextTheme( // to change text color, use text theme that has multiple properties to change
body1: TextStyle(
color: Colors.white,
),
),
),
);
Extending a theme
MaterialApp(
theme: ThemeData.dark().copyWith(
primaryColor: Color(0xFF0A0E21),
scaffoldBackgroundColor: Color(0xFF0A0E21),
),
);
Using a different custom theme in a particular widget, wrap it with Theme()
class and use data
property
floatingActionButton: Theme(
data: ThemeData.light(), // like this
data: ThemeData(accentColor: Colors.red),// or like this, with custom properties
child: ...
),
Making a widget reusable
if you are repeating yourself a lot of, you can click on a widget, later on the yellow light click extract widget
, this will create a class with the widget content that you can reuse across the app
Making the property required in the reusable widget class
Note: property
, Instance variable
and field
are the same thing in a class.
import 'package:meta/meta.dart';
class ReusableCard extends StatelessWidget {
ReusableCard({@required this.colour, this.cardChild}); // Setting it as required, if we don't, it will be optional, but later if
// a widget don't send it and we use it in this class, it will fire null.
final Color colour;
final Widget cardChild; // this for allowing child inside our custom widget
@override
Widget build(BuildContext context) {
return Container(
child: cardChild,
decoration: BoxDecoration(
color: colour,
...
}
// Using it
Expanded(
child: ReusableCard(
cardChild: Text(...),
colour: Color(0xFFFF0000),
),
)
if you noticed, we used the final
keyword in the last class widget, that's because we used a StatelessWidget
, it means it is unchangeable, immutable, so, its values don't change, when we "update" a stateless widget, we actually we destroy it and we put/create a new one with different values.
So, that's why we need to use final
or const
on StatelessWidget, because its properties can't change, because it is immutable.
const or final
const is used more for hardcoded things, things that don't need the app to be running to be applied, like:
const int myConst = 2;
const myOtherConst = 2 * 2;
Remember const
can't be reassigned later.
Also, it is a convention, to call all constants in flutter starting with the letter k
, so, it will be kMyConstant
, so every time you type k
, you fill find your constants and flutter system constants easily, 'cause the last ones are named starting with k too.
final is used for things that can be set at the run time of the app, for example when creating a new widget and passing a parameter or something like that
final int myFinal = DateTime.now(); // this needs the app running to get the hour
final myOtherFinal = parameterIgetFromClass; // this needs app running too, when widget it's created
Enum
it works like models. You have to define them outside a class.
Enums are very useful when you have more than one option for a property.
void main(){
Car myCar = Car(carStyle: CarType.convertible);
}
class Car {
// Defining the class property as an CarType type
CarType carStyle;
Car({this.carStyle});
if(carStyle == CarType.convertible)
...
}
enum CarType {
hatchbak,
SUV,
convertible
}
Passing functions as properties
We can pass a function as a properties type Function
like this:
void main() {
Car myCar = Car(drive: slowDrive);
myCar.drive(); // driving slowly
myCar.drive = fastDrive;
myCar.drive(); // driving super fast
}
class Car {
Car({this.drive});
Function drive;
}
void slowDrive() {
print('driving slowly');
}
void fastDrive() {
print('driving super fast');
}
Navigation
Navigator.push()
is going to push or stack the new screen on the app, it needs the context and the MaterialPageRoute with the info of the new screen to be shown
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return myNewScreen();
}));
},
Navigator.pop()
is going to destroy the active screen and go back to last screen before that one:
onTap: () {
Navigator.pop(context);
},
Named Routes
If named routes is going to be used, in the material app, use initialRoute
and no home
, also routes is the Map with the list of screens where to navigate.
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => FirstScreen(),
'/second': (context) => SecondScreen(),
},
);
// Later using it:
onPressed: () {
Navigator.pushNamed(context, '/second');
}
Maps
Maps are usefully for saving collections and easily access them by their Key
, writing a map is as simple as: Map<KeyType,ValueType> mapName
// Defining Map
Map<String, int> phoneBook = {
'Geily': 1234,
'Jonny': 5432,
};
main() {
// Accesing it
phoneBook['Geily']; // 1234
// New Item
phoneBook['Son'] = 0000;
// Getting Keys & values
print(phoneBook.keys, phoneBook.values) // Geily, Jonny, Son, 1234, 5432, 0000
}
Future, async & await
import 'dart:io';
void main() {
performTasks();
}
void performTasks() async { // async needs to be on the function if we want to use await later
task0();
task1();
String task2Result = await task2(); // this is going to block main thread because the await
task3(task2Result);
}
void task0() {
print('Task 0 complete!');
}
void task1() {
Duration tenSeconds = Duration(seconds: 10);
Future.delayed(tenSeconds, () { // Future is a async function, is not going to block main thread
// unless we ask otherwise with an await.
print('Task 1 complete!');
});
}
Future<String> task2() async {// Future<String> expects a string, you can leave Future alone to make it any type
Duration threeSeconds = Duration(seconds: 3);
String result;
await Future.delayed(threeSeconds, () {
result = 'task 2 data';
print('Task 2 complete');
});
return result;
}
void task3(String task2Data) {
print('Task 3 complete with $task2Data');
}
// Task 0 complete! -> Printed right away
// Task 2 complete -> Printed 3 seconds after first one, it BLOCKED main thread
// Task 3 complete with task 2 data -> Printed 3 seconds after first one
// Task 1 complete! -> Printed 10 seconds after first one, it DID NOT BLOCKED main thread
Common lifecycles of statefull widget
initState(), deactivate() and build() are used very common
class StateFulCycles extends StatefulWidget {
@override
_StateFulCyclesState createState() => _StateFulCyclesState();
}
class _StateFulCyclesState extends State<StateFulCycles> {
@override
void initState() {
super.initState();
print("initState, when widget was initialized for the first time, it only gets called one time");
}
@override
void deactivate() {
super.deactivate();
print("deactivate, when widget was deactivated/destroyed");
}
@override
Widget build(BuildContext context) {
print("build, when widget is build for first time or rebuild updating the state of it, it'll be called repeatedly");
}
}
Exception Handling
Try and catch
main(){
String myString = "abc";
double myStringAsADouble;
try {
myStringAsADouble = double.parse(myString);
print(myStringAsADouble + 5); // Doesn't print anything
} catch (e) {
print(e); // FormatException: Invalid double abc
myStringAsADouble = 30.0; // if string was not able to be converted to double, set it to 30
}
print(myStringAsADouble) // 30
}
Null Aware Operators
someVariable ?? defaultValue
...
margin: EdgeInsets.all(myStringAsADouble ?? 50.0); // if `myStringAsADouble` use it, if not, set it to 50.0
...
Networking with http package
Doing a get request
import 'package:http/http.dart' as http; // install package and import it
import 'dart:convert'; // to convert string to json (json parse)
void getData() async {
http.Response response = await http.get("https://myAPI.com"); // get method is the one for getting info from the API
if (response.statusCode == 200) { // status code gives 200, 404.. etc
String data = response.body; // this is the response info
jsonDecode(data)["user"]["name"]; // it will navigate through JSON to {user:{name: "Jonny"}},
// for arrays, use index [0]
}
}
Passing Data to a State Object
if you have some properties that you receive in your widget constructor and you need to pass it to your widget state, you can read it like this widget.myProperty
:
class MyWidget extends StatefulWidget {
MyWidget({this.myProperty});
final myProperty;
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
@override
void initState() {
super.initState();
print(widget.myProperty); // this will print any value in the widget state that has been passed to the widget constructor or that the widget class has.
}
...
}
Passing Data Backwards Through the Navigation Stack
First, we set the parent push
as variable, so, when the children, on pop
(close/destroy) send a value, we receive it in the variable:
Note Keep in mind that what the push
will return a Future
, so, we may need to use an async/await
to read the value:
onPressed: () async {
var typedName = await Navigator.push(context,MaterialPageRoute(builder: (_) {return myNewScreen();},),);
};
Children pop
:
Navigator.pop(context, myVariable)
Working with Forms
The component Form()
requires an unique key
that is used to handle the form state
final _formKey = GlobalKey<FormState>(); // Unique id for the form
FocusNode _passFocus = FocusNode();
...
Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(
hintText: translate('auth.email'), // Like the placeholder on web
),
// Input type email, please not that by default flutter doesn't support validation
// on input type yet, in this case if user write something else different to an email,
// this will still fire as true
keyboardType: TextInputType.emailAddress,
// the icon in the keyboard, in this case an arrow -> pointing to next input
textInputAction: TextInputAction.next,
// when FORM is saved, update _user.email state
onSaved: (val) => setState(() => _user.email = val),
// what to do after input is submitted,
// in this case, set focus to next form input
onFieldSubmitted: (term) {
_passFocus.requestFocus();
},
),
TextFormField(
decoration: InputDecoration(
hintText: translate('auth.password'),
),
// if we want some other input to focus in this one after it is done
// we have to add the focusNode name here and then use it in the other input
focusNode: _passFocus,
//Keyboard type Password
keyboardType: TextInputType.visiblePassword,
// to not show the password when typing
obscureText: true,
// Keyboard icon set as a checkbox marker
textInputAction: TextInputAction.done,
// Small validator, if input empty, don't submit form
validator: (value) {
if (value.isEmpty) return "Please write your password";
return null;
},
// when FORM is saved, update input state
onSaved: (val) => setState(() => _user.password = val),
// Submit form on input submit
onFieldSubmitted: (term) {
_onSubmitForm();
},
),
// Simple button to submit form as well
RaisedButton(
onPressed: () {
_onSubmitForm();
},
child: Text("Submit"),
),
],
),
);
and now the form submit function:
void _onSubmitForm() {
//if form is valid
if (_formKey.currentState.validate()) {
//This will call all methods onSaved in the form inputs and assign the corresponding value
_formKey.currentState.save();
// Process form on BE
doWhatYouWantAfterFormIsSubmitted();
}
}
Post in progress...