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") }

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

print("my string with my $variableToBeInterpolated")

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

Working with lists

// 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

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.

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
}

Post in progress...