Flutter & Dart - the complete guide Notes

GitHub - madindo/exercise_flutter: This is a place for me practicing flutter
This is a place for me practicing flutter. Contribute to madindo/exercise_flutter development by creating an account on GitHub.

To create a new Project

flutter create first_app
flutter run

Position & Named arguments

In the previous lecture, you learned about "positional" and "named" arguments / parameters.

In general, function parameters / arguments (the term is used interchangeably here) are a key concept.

You use arguments to pass values into a function. The function may then use these parameter values to work with them - e.g., to display them on the screen, use them in a calculation or send them to another function.

In Dart (and therefore Flutter, since it uses Dart), you have two kinds of parameters you can accept in functions:

Positional: The position of an argument determines which parameter receives the value

void add(a, b) { // a & b are positional parameters
  print(a + b); // print() is a built-in function that will be explained later
}
 
add(5, 10); // 5 is used as a value for a, because it's the first argument; 10 is used as a value for b

Named: The name of an argument determines which parameter receives the value

void add({a, b}) { // a & b are named parameters (because of the curly braces)
  print(a + b); 
}  
 
add(b: 5, a: 10); // 5 is used as a value for b, because it's assigned to that name; 10 is used as a value for a

Besides the different usage, there's one very important difference between positional and named arguments: By default, positional parameters are required and must not be omitted - on the other hand, named arguments are optional and can be omitted.

In the example above, when using named parameters, you could call add() like this:

add();

or

add(b: 5);

When using positional parameters, calling add() like this would be invalid and hence cause an error!

You can change these behaviors, though. You can make positional arguments optional and named arguments required.

Positional arguments can be made optional by wrapping them with square brackets ([]):

void add(a, [b]) { // b is optional
  print(a + b);
}

Once a parameter is optional, you can also assign a default value to it - this value would be used if no value is provided for the argument:

void add(a, [b = 5]) { // b is optional, 5 will be used as a default value
  print(a + b);
}
add(10); // b would still be 5 because it's not overwritten
add(10, 6);

Default values can also be assigned to named parameters - which are optional by default:

void add({a, b = 5}) { // b has a default value of 5
  print(a + b); 
}  
 
add(b: 10); // for b, 10 would be used instead of 5; a has no default value and would be "null" here => a special value type you'll learn about throughout this course

You can also make named parameters required by using the built-in required keyword:

void add({required a, required b}) { // a & b are no longer optional
  print(a + b); 
}

You will, of course, see these different use-cases in action throughout the course.

import 'package:flutter/material.dart';
void main() {
  runApp(
    const MaterialApp(
      home: Text('Hello World!')
      )
    );
}

Types

First App https://github.com/madindo/exercise_flutter/tree/main/first_app

Deep Dive: Flutter's (Stateful) Widget Lifecycle

Every Flutter Widget has a built-in lifecycle: A collection of methods that are automatically executed by Flutter (at certain points of time).

There are three extremely important (stateful) widget lifecycle methods you should be aware of:

  • initState(): Executed by Flutter when the StatefulWidget's State object is initialized
  • build(): Executed by Flutter when the Widget is built for the first time AND after setState() was called
  • dispose(): Executed by Flutter right before the Widget will be deleted (e.g., because it was displayed conditionally)

You will encounter them all multiple times throughout the course - therefore you don't have to memorize them now and you will see them in action. It's still worth learning about them right now already.

Using "if" Statements In Lists

The if statement is a crucial feature of the Dart language - actually, it's a core feature of pretty much all programming languages.

In addition to what you learned in the previous lecture, in Dart, you may also use if inside of lists to conditionally add items to lists:

1. final myList = [ 2.   1, 3.   2, 4.   if (condition) 5.     3 6. ];

In this example, the number 3 will only be added to myList if condition was met (condition can be true or false or a check that yields true or false - e.g., day == 'Sunday').

Please note that there are NO curly braces around the if statement body. The if statement body also only comprises the next line of code (i.e., you can't have multiple lines of code inside the if statement).

You can also specify an else case - an alternative value that may be inserted into the list if condition is not met:

1. final myList = [ 2.   1, 3.   2, 4.   if (condition) 5.     3 6.   else 7.     4 8. ];

Using this feature is optional. Alternatively, you could, for example, also work with a ternary expression:

1. final myList = [ 2.   1, 3.   2, 4.   condition ? 3 : 4 5. ];

Especially when inserting more complex values (e.g., a widget with multiple parameters being set) into a more complex list (e.g., a list of widgets passed to a Column() or Row()), this feature can lead to more readable code.

You will also see it being used later in the course. It will be explained again then.

You can also learn more about this feature here: https://github.com/dart-lang/language/blob/master/accepted/2.3/control-flow-collections/feature-specification.md

if Statements & Comparison Operators

When using if statements - no matter if inside or outside of functions - as well as when using ternary expressions, you ultimately must provide a boolean value (true / false):

1. if (true) { 2.   // do something ... 3. } 4. // or 5. true ? 'this' : 'that'

Of course, hardcoding true or false into the code makes no sense though - you wouldn't need an if statement or ternary expression if a value would always be true or always be false.

Instead, true or false is typically derived by comparing values - e.g, comparing a number to an expected value:

1. if (randomNumber == 5) { 2.   // do something 3. }

The == operator checks for value equality (i.e., the values on the left and right side of the operator must be equal). It must not be mistaken with the assignment operator (which uses a single equal sign: =).

The assignment operator is used to assign values to variables:

1. var userName = 'Max'; // assignment operator used 2. if (userName == 'Max') { ... } // comparison operator used

Besides the equality operator (==) Dart also supports many other key comparison operators:

  • != to check for inequality (randomNumber != 5 expects randomNumber to NOT be 5, i.e., to be any other value)
  • > to check for the value on the left to be greater than the value on the right (randomNumber > 5 yields true if randomNumber is greater than 5)
  • >= to check for the value on the left to be greater than or equal to the value on the right (randomNumber >= 5 yields true if randomNumber is greater than 5 or equals 5)
  • < to check for the value on the left to be smaller than the value on the right (randomNumber < 5 yields true if randomNumber is smaller than 5)
  • <= to check for the value on the left to be smaller than or equal to the value on the right (randomNumber <= 5 yields true if randomNumber is smaller than 5 or equals 5)

Using "for" Loops In Lists

Just as you can also use the if keyword inside of lists (to add elements conditionally), you can also use the for keyword to add multiple items into a list:

1. final numbers = [5, 6]; 2. final myList = [ 3.   1, 4.   2, 5.   for (final num in numbers) 6.     num 7. ];

In this example, the numbers 5 and 6 will be added to myList (hence myList thereafter is [1, 2, 5, 6]).

This for ... in syntax is a special variation of the for loop that loops through multiple items in a list. You will see it again later in the course - both outside and inside of a list. It will also be explained again later.

The idea behind this loop is to simplify the process of performing some operation on all items in a list.

When used in a list, it's essentially an alternative to the spread operator (...):

1. final numbers = [5, 6]; 2. final myList = [ 3.   1, 4.   2, 5.   ...numbers 6. ];

It can be useful in scenarios where values must be transformed before being added to a list - the for ... in loop can then be used instead of map() + spread operator:

1. final numbers = [5, 6]; 2. final myList = [ 3.   1, 4.   2, 5.   ...numbers.map((n) { 6.     return n * 2;  7.   }) // adds 10 and 12 8. ];

can be replaced with:

1. final numbers = [5, 6]; 2. final myList = [ 3.   1, 4.   2, 5.   for (final num in numbers) 6.     num * 2 // adds 10 and 12 7. ];

As mentioned, you will learn more about for later in the course.

You can also learn more about for ... in inside of lists here: https://github.com/dart-lang/language/blob/master/accepted/2.3/control-flow-collections/feature-specification.md#repetition

Improving the Image Picker Component

There are two improvements you could / should make to that "Image Picker" component:

  1. Reset the previewed image if no image was selected:

Add set setPickedImage(null); to the if (!file) block:

1. if (!file) { 2.   setPickedImage(null); 3.   return; 4. }

  1. Add the required prop to the (hidden) <input> element:

1. <input 2.   className={classes.input} 3.   type="file" 4.   id={name} 5.   accept="image/png, image/jpeg" 6.   name={name} 7.   ref={imageInput} 8.   onChange={handleImageChange} 9.   required 10. />

This ensures that the <form> can't be submitted without an image being selected.

Important: "location" Package & Android

In the next lecture, we'll add the "location" package (a third-party package) get the user's location.

In the next lectures, when running the app on Android, you might get an error though (after adding that package).

If that should be the case, try editing your android/settings.gradle file and make sure the following line:

1. id "org.jetbrains.kotlin.android" version "1.9.21" apply false

reflects your current Kotlin version (you find that version in the build.gradle file).

Authentication