Flutter

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)
  • 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...