Before You Start: How Programming Works
A program is just a list of instructions. Dart reads your instructions and runs them step by step.
- Compilation: Dart checks and prepares your code.
- Runtime: your program is actually executing.
- Data: values your program uses and changes.
- Control flow: decisions and repetition.
Who Should Learn Dart?
Dart is great for:
- Beginners who want a clean modern language.
- Flutter developers building mobile/web/desktop apps.
- Backend developers using Dart services and APIs.
- Teams that prefer one language across app + server.
1. First Program
Your app needs one known starting point. In Dart, that starting point is main().
Without it, Dart does not know where execution begins.
Use code below only as a minimal illustration.
void main() {
print('Hello, Dart!');
}
Understand The Line
print: built-in function that writes text to console.
( ): function input area (argument list).
'Hello, Dart!': a string (text value).
;: statement ends here.
Understand The Entry Point
void: this function returns no value.
main: special name Dart looks for first.
(): parameter list (empty in this example).
{ }: the function body (code that runs).
Beginner checks:
- If you rename
main to mains, Dart will not find the normal entry point.
- If you remove
(), function syntax is invalid.
- If you remove
{}, block-style function will not work.
2. Variables and Types
A variable is a named box that stores data.
Before code, understand the 4 basic data types:
String: text like names, emails, titles.
int: whole numbers like age, count, quantity.
double: decimal numbers like price, score, rating.
bool: only true or false.
Why variables are important:
if one value appears in 10 to 50 places, changing every place manually is hard and risky.
With one variable, you change it once and all usage updates automatically.
Real-Life Illustration
Imagine a tax rate used in many screens and calculations.
Hardcoding 0.15 everywhere means big effort later.
Store it once in a variable, then reuse it everywhere.
double taxRate = 0.15; // used many times in the app
// Later you change business rule:
taxRate = 0.18; // one change, all calculations now use new value
Basic type illustration:
void main() {
String name = 'Ada';
int age = 22;
double score = 95.5;
bool isActive = true;
print('$name is $age years old. score=$score active=$isActive');
}
Can Variables Be Updated?
void main() {
int count = 10;
print(count); // 10
count = 50; // update value
print(count); // 50
}
Why Types Help Beginners
- You catch mistakes earlier.
- Your editor can suggest better autocomplete.
- Code becomes easier to explain and review.
Try Wrong Type (See The Error)
If a variable is int, you cannot assign text to it.
Dart will stop you at compile time.
void main() {
int age = 22;
age = 'twenty two'; // ❌ wrong type
}
You will see an error like:
A value of type 'String' can't be assigned to a variable of type 'int'.
3. Working With Data Types
After you know types, the next step is using their built-in methods.
Methods are helper actions you can call on values.
String Methods (Text)
void main() {
String name = ' ada lovelace ';
print(name.trim()); // 'ada lovelace'
print(name.toUpperCase()); // ' ADA LOVELACE '
print(name.contains('love')); // true
print(name.replaceAll('a', '@')); // ' @d@ lovel@ce '
print(name.trim().length); // text length
}
Number Methods (int / double)
void main() {
double price = 19.789;
int count = 7;
print(price.toStringAsFixed(2)); // 19.79
print(price.round()); // 20
print((-10).abs()); // 10
print(count.isEven); // false
print(count.isOdd); // true
}
Boolean: Only true or false
A boolean value can only be true or false.
void main() {
bool isLoggedIn = true;
bool isBlocked = false;
print(isLoggedIn); // true
print(isBlocked); // false
print(!isLoggedIn); // false (! means "not")
}
Use booleans for yes/no decisions: access granted, loading done, email verified, etc.
4. Operators (Before If Statement)
Operators are symbols Dart uses to compare, calculate, or combine values.
You need them before condition flow because if checks expressions built with operators.
Arithmetic Operators
Dart can perform arithmetic and follows normal math order rules:
BODMAS / PEDMAS (parentheses first, then multiply/divide, then add/subtract).
+ // add
- // subtract
* // multiply
/ // divide (result is double)
~/ // integer divide (no decimal)
% // remainder (modulo)
void main() {
print(10 + 3); // 13
print(10 - 3); // 7
print(10 * 3); // 30
print(10 / 3); // 3.333...
print(10 ~/ 3); // 3
print(10 % 3); // 1
print(2 + 3 * 4); // 14 (multiply first)
print((2 + 3) * 4); // 20 (parentheses first)
}
Comparison Operators
a == b // equal
a != b // not equal
a > b // greater than
a < b // less than
a >= b // greater or equal
a <= b // less or equal
Logical Operators
&& // and
|| // or
! // not
Small Illustration
void main() {
int age = 20;
bool hasId = true;
bool canEnter = age >= 18 && hasId;
print(canEnter); // true
}
5. If, Else, and Loops
What is an if statement? It is how your program makes decisions.
If a condition is true, run one block. Otherwise run another block.
Why use it? Real apps are not one fixed path. User role, score, payment status,
and login state all change behavior.
When to use it? Anytime your logic has choices.
Comparison Operators (Conditions)
a > b // greater than
a < b // less than
a >= b // greater than or equal
a <= b // less than or equal
a == b // equal
a != b // not equal
Logical Operators
&& // and (both must be true)
|| // or (at least one true)
! // not (reverse true/false)
if / else (Basic)
void main() {
int points = 72;
if (points >= 60) {
print('Pass');
} else {
print('Fail');
}
}
else if (Many choices)
void main() {
int score = 85;
if (score >= 90) {
print('Grade A');
} else if (score >= 80) {
print('Grade B');
} else if (score >= 70) {
print('Grade C');
} else {
print('Need improvement');
}
}
Nested if (if inside if)
void main() {
int age = 20;
bool hasId = true;
if (age >= 18) {
if (hasId) {
print('Enter');
} else {
print('Need ID');
}
} else {
print('Too young');
}
}
Use nested if only when logic is truly inside another condition.
If it gets deep, move logic into functions.
Using && and ||
void main() {
int age = 20;
bool hasTicket = true;
bool isVip = false;
if (age >= 18 && hasTicket) {
print('Allowed');
}
if (isVip || hasTicket) {
print('Can enter special gate');
}
}
switch case
Use switch when one value can match many known options.
It keeps code cleaner than long else if chains.
void main() {
String role = 'admin';
switch (role) {
case 'admin':
print('Full access');
break;
case 'editor':
print('Edit access');
break;
case 'viewer':
print('Read only');
break;
default:
print('Unknown role');
}
}
Loops (When To Use Each Type)
Use loops when you need repetition. The best loop depends on your goal.
1) for loop
Use for when you know how many times to repeat.
Great for counters, index-based loops, fixed rounds.
void main() {
for (int i = 1; i <= 3; i++) {
print('Round $i');
}
}
2) while loop
Use while when you do not know exact count in advance.
It keeps running while the condition is true.
void main() {
int count = 1;
while (count <= 3) {
print('Count $count');
count++;
}
}
3) do-while loop
Use do-while when the block must run at least once,
then continue only if condition is true.
void main() {
int x = 5;
do {
print('Runs once even if condition is false');
} while (x < 3);
}
4) for-in loop
Use for-in to read each item in a list/collection.
Best when you only need item values (not index).
void main() {
final fruits = ['apple', 'banana', 'orange'];
for (final fruit in fruits) {
print(fruit);
}
}
5) forEach
Use forEach for clean iteration with a callback style.
Good for simple actions on every item.
void main() {
final nums = [1, 2, 3];
nums.forEach((n) {
print('Number: $n');
});
}
Quick Selection Rule
- Known count:
for
- Unknown count by condition:
while
- Must run once first:
do-while
- Iterate collection values:
for-in
- Simple callback style iteration:
forEach
break and continue
break stops the loop immediately.
continue skips current iteration and moves to next one.
Use them when you need more control inside loop logic.
void main() {
for (int i = 1; i <= 6; i++) {
if (i == 4) {
break; // stop loop at 4
}
print('break demo: $i');
}
for (int i = 1; i <= 6; i++) {
if (i == 4) {
continue; // skip only 4
}
print('continue demo: $i');
}
}
Null With Data Types (Not Only String)
In Dart, null is not a normal value type like String or int.
It means “no value”.
Any type can be nullable by adding ?.
So it is not only String?, it can also be int?, double?, bool?, etc.
void main() {
String? name; // can be null
int? age; // can be null
double? price; // can be null
bool? active; // can be null
print(name); // null
print(age); // null
print(price); // null
print(active); // null
}
Safe Fallbacks
Use ?? to provide default values when data is null.
void main() {
int? age;
bool? active;
print(age ?? 0); // 0
print(active ?? false); // false
}
Why Learn This Before Functions?
Functions often receive values from APIs, forms, and databases. Those values can be null.
If learners understand nullable data first, function parameters and return values become much easier.
6. Functions
A function groups a task into a reusable unit. Instead of rewriting logic, you call the function.
Why this matters: functions reduce duplication and make code easier to test and maintain.
void and Return Type
Every function has a return type.
void means no value is returned.
int, String, bool, etc. mean a value must be returned.
void sayHi() {
print('Hi');
}
int add(int a, int b) {
return a + b;
}
return keyword
return sends a value back to where the function was called.
If return type is not void, you usually need return.
double priceWithTax(double price) {
return price * 1.18;
}
Arrow return (=>)
Use arrow syntax for one-line functions.
int square(int x) => x * x;
String greet(String name) => 'Hello $name';
When NOT To Use Arrow Functions
Do not force => for every function. Use block style { } when logic is longer,
has multiple steps, conditions, loops, or needs easier debugging.
// Good arrow usage (simple one expression)
bool isAdult(int age) => age >= 18;
// Better as block (multiple decisions)
String grade(int score) {
if (score >= 90) return 'A';
if (score >= 80) return 'B';
return 'C';
}
Rule: if it is not instantly readable in one expression, use normal block style.
Parameter vs Argument
Parameter is the variable in function definition.
Argument is the real value passed when calling.
void welcome(String name) { // name = parameter
print('Welcome $name');
}
void main() {
welcome('Ada'); // 'Ada' = argument
}
Named Parameters { }
Named parameters improve readability and reduce argument order mistakes.
void createUser({required String name, int age = 18}) {
print('name=$name age=$age');
}
void main() {
createUser(name: 'Ada');
createUser(name: 'Linus', age: 25);
}
Optional Positional Parameters [ ]
Use [ ] when order is okay and some arguments can be omitted.
void logMessage(String message, [String level = 'INFO']) {
print('[$level] $message');
}
void main() {
logMessage('App started'); // uses default level
logMessage('Disk almost full', 'WARN');
}
Quick Rule
- Use
void for action-only functions.
- Use return types when you need computed values.
- Use named parameters for clarity in larger APIs.
- Use optional positional
[ ] for short simple options.
Basic illustration:
int add(int a, int b) {
return a + b;
}
void main() {
final total = add(4, 6);
print(total); // 10
}
Name functions by intent: calculateTotal, fetchUsers, isValidEmail.
Think: function = mini machine. You give input, it gives output.
7. Lists and Maps
Use List when data is ordered and you access by index (position).
Use Map when data is key-value and you access by key name.
Why this matters: most real apps manage collections (users, products, messages, settings).
Picking the right structure makes code easier and safer.
List: Full Explanation
A List is an ordered collection. Index starts at 0.
Good for things like cart items, leaderboard rows, notifications.
10+ common List methods/properties:
void main() {
final nums = [3, 1, 2];
nums.add(4); // 1) add
nums.addAll([5, 6]); // 2) addAll
nums.insert(0, 99); // 3) insert
nums.remove(1); // 4) remove(value)
nums.removeAt(0); // 5) removeAt(index)
print(nums.contains(3)); // 6) contains
print(nums.indexOf(4)); // 7) indexOf
nums.sort(); // 8) sort
final filtered = nums.where((n) => n > 3).toList(); // 9) where
final doubled = nums.map((n) => n * 2).toList(); // 10) map
print(nums.first); // 11) first
print(nums.last); // 12) last
print(nums.length); // 13) length
// Optional:
// nums.clear(); // clear all items
print(nums);
print(filtered);
print(doubled);
}
Map: Full Explanation
A Map stores key-value pairs. Keys should be unique.
Good for objects/settings/profile data.
10+ common Map methods/properties:
void main() {
final user = {'name': 'Ada', 'role': 'dev'};
user['age'] = 22; // 1) set by key
user.putIfAbsent('city', () => 'Lagos'); // 2) putIfAbsent
user.update('role', (v) => 'admin'); // 3) update
user.addAll({'active': true}); // 4) addAll
print(user.containsKey('name')); // 5) containsKey
print(user.containsValue('admin')); // 6) containsValue
print(user.keys); // 7) keys
print(user.values); // 8) values
print(user.entries); // 9) entries
user.forEach((k, v) => print('$k => $v')); // 10) forEach
final upperKeys = user.map((k, v) => // 11) map
MapEntry(k.toUpperCase(), v));
user.remove('city'); // 12) remove
print(user.length); // 13) length
// Optional:
// user.clear(); // clear all pairs
print(upperKeys);
}
Quick Rule
- Need order and index? Use
List.
- Need named fields by key? Use
Map.
- Transform collections often with
where and map.
8. Classes and Objects
A class defines a model. An object is a real instance of that model.
This is how you represent real concepts like User, Order, or Invoice.
Why this matters: object-oriented design keeps data and behavior together.
Illustration:
class User {
final String name;
final int age;
User(this.name, this.age);
String intro() => 'I am $name and I am $age.';
}
void main() {
final user = User('Ada', 22);
print(user.intro());
}
9. Null Safety
Null safety means Dart forces you to be explicit about values that might be missing.
Why this matters: many production bugs are null-related. Null safety catches them early.
Illustration:
void main() {
String? nickname;
print(nickname ?? 'No nickname');
String name = 'Ada';
print(name.length);
}
String? means the value can be null. Use ?? for safe fallback values.
10. Async and Await
Some operations are slow (API calls, files, databases). Async programming lets your app wait without freezing everything.
Why this matters: modern apps are network-heavy and must stay responsive.
Illustration:
Future<String> fetchUser() async {
await Future.delayed(const Duration(seconds: 1));
return 'Ada';
}
Future<void> main() async {
final user = await fetchUser();
print(user);
}
await pauses only this async function, not the whole app.
11. Practice Task
Learning becomes real when you build. This small task combines the core concepts in one flow.
Build a tiny score app:
- Create a
Player class with name and score.
- Store players in a
List<Player>.
- Write a function to print top scorer.
- Add async function that simulates loading players.
12. Next Step
After these lessons, move to backend work with Flint:
Flint Guides
and
FlintClient Guide.
Lesson 1 Quiz (Self Check)
Try answering first. Then open each answer.
1) What is the entry point of a Dart app?
The entry point is main(). Dart starts execution there.
2) What does void mean in void main()?
void means the function returns no value.
3) Why do we use variables?
Variables store values so the program can reuse and update data.
4) Why use if/else?
Use if/else to make decisions based on conditions.
5) Why use loops?
Loops repeat actions without writing the same code many times.
Mini Glossary (Very Simple)
- Function: named block of work.
- Variable: named box for data.
- Type: kind of data (
String, int...).
- Compile: prepare/check code before running.
- Runtime: code is currently executing.
- Entry point: first function Dart runs (
main).