Flutter & Dart Development
Variables & Type System
Theory & Concepts
Understanding Dart's Variable System
Dart is a strongly typed language with optional type annotations and powerful type inference. This gives you the best of both worlds: safety and conciseness.
💡 Why This Matters: Proper variable usage prevents bugs, makes code self-documenting, and enables better IDE support with autocomplete and error checking.
Variable Declaration Options
Dart offers four main ways to declare variables:
1. var - Type Inferred
- Dart automatically determines the type from the initial value
- Type cannot change after assignment
- Use when type is obvious from context
2. final - Runtime Constant
- Value set once at runtime and cannot change
- Type can be inferred or explicit
- Similar to JavaScript's
const
3. const - Compile-Time Constant
- Value must be known at compile time
- Deeply immutable (all nested values are const too)
- More restrictive than
final
4. Explicit Type - Clear Declaration
- Specify the exact type
- Use when type isn't obvious or for API clarity
- Required for uninitialized variables
Type System Fundamentals
Built-in Types:
Numbers:
int- Integers (whole numbers):42,-100double- Floating-point:3.14,-0.5num- Supertype of both int and double
Text:
String- Text data:'Hello',"World"- Supports interpolation:
'Value: $variable' - Multi-line strings with triple quotes
Boolean:
bool- True or false values- Only
trueandfalse(no truthy/falsy)
Collections:
List- Ordered collection:[1, 2, 3]Set- Unique values:{1, 2, 3}Map- Key-value pairs:{'key': 'value'}
Dynamic vs Object
dynamic:
- Disables type checking
- Use sparingly (loses type safety)
- Type errors found at runtime
Object:
- Root of Dart's type hierarchy
- More type-safe than dynamic
- Requires explicit casting
⚠️ Best Practice: Avoid dynamic when possible. Use specific types or generics instead.
Type Inference Rules
Dart's type inference is smart:
- From initialization:
var x = 5;→int - From function return:
var result = someFunction(); - Generic type inference:
var list = [1, 2];→List<int>
When to Use Each Declaration
Use var:
- Type is obvious:
var name = 'Alice'; - Local variables with clear initialization
Use final:
- Value won't change after initialization
- Can't be known at compile time:
final timestamp = DateTime.now(); - Function parameters that shouldn't change
Use const:
- Compile-time constants:
const pi = 3.14159; - Deeply immutable data structures
- Performance optimization (single instance)
Use explicit types:
- Public APIs and class fields
- Uninitialized variables:
int? count; - When type isn't obvious
Common Pitfalls
Mistake 1: Trying to Reassign final/const
final x = 10;x = 20; // ❌ Error: Can't assign to final variableMistake 2: Using const for Runtime Values
const now = DateTime.now(); // ❌ Error: Not a compile-time constantfinal now = DateTime.now(); // ✅ CorrectMistake 3: Overusing dynamic
dynamic data = getData(); // ❌ Loses type safetyvar data = getData(); // ✅ Better: maintains typeSummary
Key Takeaways:
var= type inferred, can't reassign typefinal= set once at runtime, immutableconst= compile-time constant, deeply immutable- Use explicit types for clarity and uninitialized variables
- Avoid
dynamicwhen possible
Best Practices:
- Prefer
finalovervarwhen value won't change - Use
constfor compile-time constants - Be explicit with public API types
- Let type inference work for obvious cases
Remember: Strong typing catches bugs early and makes code self-documenting!
Lesson Content
Master Dart's variable declarations, type system, and type inference. Learn the differences between var, final, const, and explicit types.
Code Example
// Dart Variable Declarations & Type System// Comprehensive examples of variables, types, and best practicesvoid main() { print('=' * 80); print('DART VARIABLES & TYPE SYSTEM'); print('=' * 80); print(''); // 1. VAR - Type Inferred print('1. VAR (Type Inferred)'); print('-' * 80); var name = 'Alice'; // String inferred var age = 25; // int inferred var height = 5.6; // double inferred var isStudent = true; // bool inferred print('name: $name (type: ${name.runtimeType})'); print('age: $age (type: ${age.runtimeType})'); print('height: $height (type: ${height.runtimeType})'); print('isStudent: $isStudent (type: ${isStudent.runtimeType})'); // ❌ Cannot change type after inference // age = '26'; // Error: Can't assign String to int age = 26; // ✅ Can change value (same type) print('age updated: $age'); print(''); // 2. FINAL - Runtime Constant print('2. FINAL (Runtime Constant)'); print('-' * 80); final currentTime = DateTime.now(); // Evaluated at runtime final userName = 'Bob'; final userAge = 30; print('Current time: $currentTime'); print('User: $userName, Age: $userAge'); // ❌ Cannot reassign final variables // userName = 'Charlie'; // Error: Can't reassign final // ✅ But can modify contents of mutable objects final List<int> scores = [85, 90, 88]; scores.add(92); // ✅ Allowed (list itself is final, contents aren't) print('Scores: $scores'); print(''); // 3. CONST - Compile-Time Constant print('3. CONST (Compile-Time Constant)'); print('-' * 80); const pi = 3.14159; const appName = 'FlutterApp'; const maxUsers = 1000; print('Pi: $pi'); print('App Name: $appName'); print('Max Users: $maxUsers'); // ❌ Cannot use runtime values with const // const currentDate = DateTime.now(); // Error: Not compile-time constant // ✅ Const collections are deeply immutable const List<int> primes = [2, 3, 5, 7, 11]; // primes.add(13); // Error: Cannot modify const list print('Prime numbers: $primes'); print(''); // 4. EXPLICIT TYPES print('4. EXPLICIT TYPES'); print('-' * 80); String firstName = 'Charlie'; int employeeId = 12345; double salary = 75000.50; bool isActive = true; print('Employee: $firstName (#$employeeId)'); print('Salary: $$salary'); print('Active: $isActive'); // Uninitialized variables require explicit types String? middleName; // Nullable String int? departmentId; // Nullable int print('Middle name: $middleName (null until assigned)'); print('Department: $departmentId (null until assigned)'); print(''); // 5. NUMBER TYPES print('5. NUMBER TYPES (int, double, num)'); print('-' * 80); int wholeNumber = 42; double decimal = 3.14159; num flexibleNumber = 10; // Can be int or double print('Integer: $wholeNumber'); print('Double: $decimal'); print('Num (int): $flexibleNumber'); flexibleNumber = 10.5; // ✅ Can reassign to double print('Num (double): $flexibleNumber'); // Number operations int sum = wholeNumber + 8; // 50 double product = decimal * 2; // 6.28318 num division = wholeNumber / 2; // 21.0 (always returns double) int intDivision = wholeNumber ~/ 3; // 14 (integer division) int remainder = wholeNumber % 5; // 2 (modulo) print('Operations:'); print(' 42 + 8 = $sum'); print(' 3.14159 * 2 = $product'); print(' 42 / 2 = $division (double)'); print(' 42 ~/ 3 = $intDivision (int division)'); print(' 42 % 5 = $remainder (remainder)'); print(''); // 6. STRING TYPES print('6. STRING TYPES & INTERPOLATION'); print('-' * 80); String greeting = 'Hello'; String message = "Welcome to Dart!"; // String interpolation String interpolated = 'User $firstName is $age years old'; String expression = 'Next year: ${age + 1}'; print(greeting); print(message); print(interpolated); print(expression); // Multi-line strings String multiLine = '''This is amulti-linestring in Dart '''; print('Multi-line:$multiLine'); // String concatenation String fullGreeting = greeting + ', ' + firstName + '!'; print(fullGreeting); // String methods print('Uppercase: ${greeting.toUpperCase()}'); print('Lowercase: ${greeting.toLowerCase()}'); print('Length: ${greeting.length}'); print('Contains "ell": ${greeting.contains("ell")}'); print(''); // 7. BOOLEAN TYPE print('7. BOOLEAN TYPE'); print('-' * 80); bool isLoggedIn = true; bool hasPermission = false; bool isAdmin = age > 18 && isLoggedIn; // Logical operations print('Logged in: $isLoggedIn'); print('Has permission: $hasPermission'); print('Is admin: $isAdmin'); // ⚠️ Dart has NO truthy/falsy values // Only true and false are valid boolean values print(''); // 8. COLLECTIONS print('8. COLLECTION TYPES (List, Set, Map)'); print('-' * 80); // List (ordered, allows duplicates) List<int> numbers = [1, 2, 3, 4, 5]; var fruits = ['apple', 'banana', 'orange']; // Type inferred: List<String> print('List: $numbers'); print('Fruits: $fruits'); print('First fruit: ${fruits[0]}'); print('List length: ${fruits.length}'); // Set (unordered, unique values) Set<String> uniqueNames = {'Alice', 'Bob', 'Charlie'}; uniqueNames.add('Alice'); // Duplicate ignored print('Set: $uniqueNames'); print('Contains Bob: ${uniqueNames.contains("Bob")}'); // Map (key-value pairs) Map<String, int> ages = { 'Alice': 25, 'Bob': 30, 'Charlie': 35, }; print('Map: $ages'); print("Bob's age: ${ages['Bob']}"); print('Keys: ${ages.keys}'); print('Values: ${ages.values}'); print(''); // 9. TYPE INFERENCE IN COLLECTIONS print('9. GENERIC TYPE INFERENCE'); print('-' * 80); var inferredList = [1, 2, 3]; // List<int> var inferredSet = {1, 2, 3}; // Set<int> var inferredMap = {'key': 1}; // Map<String, int> print('Inferred list type: ${inferredList.runtimeType}'); print('Inferred set type: ${inferredSet.runtimeType}'); print('Inferred map type: ${inferredMap.runtimeType}'); print(''); // 10. DYNAMIC vs OBJECT print('10. DYNAMIC vs OBJECT'); print('-' * 80); dynamic dynamicVar = 'Hello'; dynamicVar = 42; // ✅ Can change type (no compile-time checking) dynamicVar = true; print('Dynamic (loses type safety): $dynamicVar'); Object objectVar = 'Hello'; objectVar = 42; // ✅ Can hold any object // print(objectVar.length); // ❌ Error: Object has no length property print('Object (type-safe): $objectVar'); // ⚠️ Prefer specific types over dynamic print(''); print('=' * 80); print('SUMMARY'); print('=' * 80); print(''); print('Variable Declarations:'); print(' • var - Type inferred from value'); print(' • final - Set once at runtime (immutable reference)'); print(' • const - Compile-time constant (deeply immutable)'); print(' • Type - Explicit type declaration'); print(''); print('Core Types:'); print(' • int, double, num - Numbers'); print(' • String - Text'); print(' • bool - Boolean (true/false only)'); print(' • List, Set, Map - Collections'); print(''); print('Best Practices:'); print(' ✓ Use final when value won\'t change'); print(' ✓ Use const for compile-time constants'); print(' ✓ Let type inference work (use var for obvious types)'); print(' ✓ Be explicit with public APIs'); print(' ✗ Avoid dynamic unless absolutely necessary');}