Understanding Dart Language Fundamentals
1. Using Variables and Data Types
| Keyword | Description | Example |
|---|---|---|
| var | Declares a variable with type inference | var name = 'Flutter'; |
| final | Declares a variable that can only be assigned once | final count = 42; |
| const | Declares a compile-time constant | const PI = 3.14; |
Example: Declaring Variables
// Using var
var message = 'Hello, Dart!';
var number = 10;
// Using final
final isFlutter = true;
final version = '3.19';
// Using const
const maxUsers = 1000;
const appName = 'MyApp';
| Type | Description | Usage |
|---|---|---|
| String | Represents a sequence of characters | String name = 'Flutter'; |
| int | Represents whole numbers | int age = 25; |
| double | Represents floating-point numbers | double price = 19.99; |
| bool | Represents boolean values (true or false) | bool isActive = true; |
| List<T> | Represents an ordered collection of elements | List<String> fruits = ['apple', 'banana']; |
| Map<K, V> | Represents a key-value mapping | Map<String, int> scores = {'Alice': 95, 'Bob': 87}; |
Example: Data Type Usage
// String operations
String greeting = 'Welcome to ';
String language = 'Dart';
String fullGreeting = greeting + language;
// Numeric operations
int a = 10;
int b = 20;
double sum = a + b; // Implicit conversion to double
// Boolean logic
bool isComplete = true;
bool hasError = false;
// List operations
List<int> numbers = [1, 2, 3];
numbers.add(4);
// Map operations
Map<String, dynamic> user = {
'name': 'John',
'age': 30,
};
user['email'] = 'john@example.com';
2. Understanding Null Safety
| Feature | Description | Example |
|---|---|---|
| Nullable Types | Types that can hold null values |
String? name; |
| Non-Nullable Types | Types that must have a value | String name; |
| Null-aware Operators | Operators to safely handle null values | name ?? 'Default'; |
Example: Null Safety in Action
void main() {
String? nullableName;
String nonNullableName = 'Flutter';
// Safe access with null-aware operator
print(nullableName ?? 'No name provided');
// Force non-null value (unsafe)
print(nonNullableName!);
}
Note: Null safety helps prevent runtime errors by catching null-related issues at compile time.
Warning: Using
! to force non-null values can cause runtime errors if the value is actually null.| Operator | Description | Usage |
|---|---|---|
| ?? | Null-coalescing operator | value ?? defaultValue |
| ??= | Null-aware assignment | value ??= 'default'; |
| ! | Non-null assertion | variable! |
3. Using Collections
In Dart, collections are used to store and manage groups of objects. The main collection types are List, Set, and Map.
Example: Creating and Using Lists
List<String> fruits = ["apple", "banana", "cherry"];
fruits.add("date");
print(fruits[0]); // apple
print(fruits.length); // 4
| Collection Type | Description | Example |
|---|---|---|
| List | Ordered collection that allows duplicates and provides indexed access. | List<int> numbers = [1, 2, 3]; |
| Set | Unordered collection that does not allow duplicates. | Set<String> colors = {"red", "blue", "green"}; |
| Map | Key-value pairs where keys are unique. | Map<String, int> ages = {"Alice": 30, "Bob": 25}; |
Example: Working with Maps
Map<String, String> person = {
"name": "John",
"city": "New York"
};
person["age"] = "30";
print(person["name"]); // John
4. Working with Functions
| Function Type | Description | Example |
|---|---|---|
| Regular Function | Standard function declaration with a name, parameters, and return value. | |
| Arrow Function | Shorthand syntax for functions that return a single expression. | int multiply(int a, int b) => a * b; |
| Anonymous Function | Function without a name, often used as a callback or assigned to a variable. | var greet = (String name) => 'Hello, $name!'; |
| Higher-Order Function | A function that takes another function as a parameter or returns one. | |
Example: Using Function Parameters
int calculate(int a, int b, Function(int, int) operation) {
return operation(a, b);
}
int add(int x, int y) => x + y;
int multiply(int x, int y) => x * y;
void main() {
print(calculate(5, 3, add)); // Output: 8
print(calculate(5, 3, multiply)); // Output: 15
}
Example: Arrow Function with Multiple Expressions
String formatName(String firstName, String lastName) =>
'${firstName.toUpperCase()} ${lastName.toUpperCase()}';
void main() {
print(formatName('john', 'doe')); // Output: JOHN DOE
}
Note: In Dart, functions are first-class objects. This means they can be assigned to variables, passed as arguments, and returned from other functions.
5. Understanding Classes and Objects
| Feature | Description | Example |
|---|---|---|
| Class Declaration | Defines a blueprint for creating objects. | class Person {<br> String name;<br> int age;<br>}<br> |
| Object Creation | Instantiates a class using the new keyword or shorthand. |
Person person = Person();<br>Person person2 = new Person();<br> |
| Constructor | Special method to initialize objects. | class Person {<br> String name;<br> Person(this.name);<br>}<br> |
| Named Constructor | Alternative constructors with custom names. | class Person {<br> String name;<br> Person.fromJson(Map<String, dynamic> json) {<br> name = json['name'];<br> }<br>}<br> |
| Inheritance | Allows a class to inherit properties and methods from another. | class Animal {<br> void eat() { }<br>}<br><br>class Dog extends Animal {<br> void bark() { }<br>}<br> |
| Abstract Class | Cannot be instantiated; can contain abstract methods. | abstract class Shape {<br> double area();<br>}<br> |
| Interface (Implicit) | Dart uses abstract classes to define interfaces. | abstract class Drawable {<br> void draw();<br>}<br><br>class Circle implements Drawable {<br> @override<br> void draw() { }<br>}<br> |
| Static Members | Belong to the class, not instances. | class MathUtils {<br> static int add(int a, int b) {<br> return a + b;<br> }<br>}<br> |
| Getters and Setters | Control access to private fields. | class Rectangle {<br> double _width;<br> double get width => _width;<br> set width(double value) {<br> if (value >= 0) _width = value;<br> }<br>}<br> |
| Final and Const | Prevent reassignment of variables. | class Example {<br> final String name = 'Flutter';<br> const int count = 10;<br>}<br> |
Example: Using a Class with Inheritance
class Vehicle {<br> String brand;<br> int year;<br><br> Vehicle(this.brand, this.year);<br><br> void start() {<br> print('Vehicle started');<br> }<br>}<br><br>class Car extends Vehicle {<br> int doors;<br><br> Car(String brand, int year, this.doors) : super(brand, year);<br><br> void openTrunk() {<br> print('Trunk opened');<br> }<br>}<br><br>void main() {<br> Car myCar = Car('Toyota', 2023, 4);<br> myCar.start();<br> myCar.openTrunk();<br>}<br>
Note: Dart does not support multiple inheritance, but it supports mixins for code reuse.
6. Using Constructors
| Constructor Type | Description | Example |
|---|---|---|
| Default Constructor | Automatically generated if no constructor is defined. Initializes all fields to their default values. | |
| Named Constructor | Allows defining multiple constructors with distinct names for different initialization logic. | |
| Factory Constructor | Used to return an instance of a different class or create instances based on conditions. | |
| Const Constructor | Creates compile-time constants. Requires all fields to be final and initialized with constant values. | |
Example: Using Named Constructors
class Student {
String name;
int grade;
Student(this.name, this.grade);
Student.highSchool(String name) : this(name, 12);
Student.college(String name) : this(name, 16);
}
void main() {
var highSchoolStudent = Student.highSchool("Alice");
var collegeStudent = Student.college("Bob");
print(highSchoolStudent.name); // Alice
print(collegeStudent.name); // Bob
}
Example: Using Factory Constructors
abstract class Animal {
String sound();
}
class Dog extends Animal {
@override
String sound() => "Woof!";
}
class Cat extends Animal {
@override
String sound() => "Meow!";
}
class AnimalFactory {
static Animal createAnimal(String type) {
if (type == "dog") {
return Dog();
} else if (type == "cat") {
return Cat();
} else {
throw Exception("Unknown animal type");
}
}
}
void main() {
var dog = AnimalFactory.createAnimal("dog");
print(dog.sound()); // Woof!
}
7. Understanding Mixins and Extensions
Mixins and extensions are powerful features in Dart that allow you to share code across classes and enhance existing types.
Example: Basic Mixin Usage
// Define a mixin
mixin Logger {
void log(String message) {
print('Log: $message');
}
}
// Use the mixin in a class
class MyClass with Logger {
void doSomething() {
log('Doing something...');
}
}
// Example usage
void main() {
var obj = MyClass();
obj.doSomething(); // Output: Log: Doing something...
}
Example: Extension Usage
// Define an extension on String
extension StringExtensions on String {
bool get isEmail {
return contains('@') && contains('.');
}
String capitalize() {
if (isEmpty) return this;
return '${this[0].toUpperCase()}${substring(1).toLowerCase()}';
}
}
// Example usage
void main() {
String email = 'user@example.com';
print(email.isEmail); // true
String name = 'john doe';
print(name.capitalize()); // John doe
}
| Feature | Description | Usage |
|---|---|---|
| Mixin | Allows sharing code between multiple classes through composition. | class A with B, C |
| Extension | Enables adding new functionality to existing types without modifying them. | extension Name on Type |
Note: Mixins are limited to classes that extend Object and must not have constructors.
Warning: Extensions cannot override methods or add fields to the extended type.
Mixin vs Extension Comparison
- Mixins: Provide reusable code across multiple class hierarchies.
- Extensions: Add methods and properties to existing types without modifying their source.
8. Using async and await Keywords
| Feature | Description | Example |
|---|---|---|
| async | Marks a function as asynchronous, allowing it to use await. |
|
| await | Pauses execution until a Future completes, returning its value. | String data = await fetchData(); |
Example: Basic async/await usage
Future<String> fetchUserData() async {
await Future.delayed(Duration(seconds: 2));
return 'User data';
}
void main() async {
String userData = await fetchUserData();
print(userData); // Prints 'User data' after 2 seconds
}
Example: Handling multiple async operations
Future<String> fetchName() async {
await Future.delayed(Duration(milliseconds: 500));
return 'John';
}
Future<String> fetchEmail() async {
await Future.delayed(Duration(milliseconds: 300));
return 'john@example.com';
}
void main() async {
String name = await fetchName();
String email = await fetchEmail();
print('Name: $name, Email: $email');
}
Note:
async and await simplify asynchronous code by making it look synchronous.
Warning: Using
await inside a loop can be inefficient; consider using Future.wait() for concurrent operations.
9. Working with Streams and Futures
Example: Listening to a Stream
Stream<int> numberStream = Stream.fromIterable([1, 2, 3, 4, 5]);
numberStream.listen((value) {
print('Received: $value');
});
| Feature | Description | Usage |
|---|---|---|
| Stream | A sequence of asynchronous events | Stream<int> |
| Future | Represents a value that will be available at some point in the future | Future<String> |
| async/await | Syntax for handling asynchronous operations | await someFuture; |
Example: Using Future
Future<String> fetchUserData() async {
await Future.delayed(Duration(seconds: 2));
return 'John Doe';
}
void main() async {
String name = await fetchUserData();
print(name);
}
Example: Stream with Error Handling
Stream<int> errorStream = Stream<int>.error(Exception('Something went wrong'));
errorStream.handleError((error) {
print('Error occurred: $error');
});
Key Concepts Summary
- Streams are used for handling sequences of asynchronous events.
- Futures represent a single asynchronous operation that will complete at some point.
- async/await syntax makes asynchronous code look synchronous.
10. Using Cascades (..) and Spread Operators (...)
| Feature | Description | Example |
|---|---|---|
| Cascades (..) | Allows chaining operations on the same object without storing it in a variable. | |
| Spread Operators (...) | Used to insert all elements of one collection into another. | |