Dart Cheat Sheet
This cheat sheet provides a quick reference for essential Dart concepts.
Datatypes
Dart supports various data types to represent different kinds of information:
- Numbers:
int
(integers),double
(floating-point numbers),num
(eitherint
ordouble
). - Boolean:
bool
(true or false). - String:
String
(sequence of characters). - List:
List
(ordered collection of elements). - Map:
Map
(unordered collection of key-value pairs).
Variables
Variables store values and can be declared using var
, final
, or const
:
var name = "Alice"; // Inferred type (String)
final age = 30; // Constant value, cannot be reassigned
const PI = 3.14159; // Compile-time constant
You can explicitly specify the type of a variable using the type annotation:
String name = "Alice";
final int age = 30;
const double pi = 3.14159;
Nullable variables
Dart allows variables to be null by default. Use the null-safety operator (!
) to access properties or call methods on nullable variables:
String? message; // Declares a nullable String
print(message?.length); // Prints null if message is null, otherwise its length
Late variables
Late variables are non-nullable variables that can be declared without an initial value. They must be assigned a value before being used, otherwise a runtime error occurs. This allows you to defer initialization until later in your code, potentially improving performance or readability in specific situations.
class User {
late String name; // Declares a late variable
void setName(String newName) {
name = newName; // Assigns value to the late variable
}
void printInfo() {
print("Name: $name"); // Can be used after name is set
}
}
User user = User();
user.setName("Alice"); // Assigns value before using
user.printInfo(); // Prints "Name: Alice"
Use late variables cautiously, as forgetting to initialize them can lead to runtime errors. Consider using null-safety or regular variables with initial values for most cases.
const
and final
Both const
and final
are used to declare read-only variables, but they differ in their mutability and compile-time behavior:
const
:- Declares a compile-time constant.
- The value must be known at compile time and cannot be changed.
- Useful for constant values like PI or application configurations.
final
:- Declares a constant variable that can be initialized at runtime (constructor, initializer list, etc.).
- The value cannot be changed after initialization.
- Used for variables whose value is determined later but should not change afterwards.
Choosing between const
and final
depends on when the value is known and whether it needs to be a compile-time constant.
Strings
Strings are sequences of characters:
String greeting = "Hello";
String multiline = '''
This is a
multiline string.
''';
String interpolation
Embed variables or expressions within strings using string interpolation:
String name = "Bob";
String salutation = "Hello, $name!"; // Prints "Hello, Bob!"
String operations
Strings support various operations like concatenation, comparison, and accessing characters:
String message1 = "Hello";
String message2 = " World";
String combined = message1 + message2; // Concatenation
bool isEqual = message1 == "Hello"; // Comparison
print(combined[0]); // Access first character (H)
Functions
Functions are reusable blocks of code that perform specific tasks. They can take arguments and return values:
int add(int x, int y) {
return x + y;
}
int result = add(5, 3); // Calls the function with arguments
print(result); // Prints 8
Returning values
Functions can return values using the return
statement:
String greet(String name) {
return "Welcome, $name!";
}
String message = greet("Alice");
print(message); // Prints "Welcome, Alice!"
Named parameters
Functions can have named parameters, allowing them to be called in any order:
void printInfo({required String name, int age = 0}) {
print("Name: $name, Age: $age");
}
printInfo(name: "Bob", age: 35); // Prints "Name: Bob, Age: 35"
printInfo(age: 20, name: "Charlie"); // Prints "Name: Charlie, Age: 20"
Optional positional parameters
Functions can have optional positional parameters with default values:
void greet(String name, [String? message]) {
print("Hello, $name!");
if (message != null) {
print(message);
}
}
greet("John"); // Prints "Hello, John!"
greet("Mary", "Have a nice day!"); // Prints "Hello, Mary!\nHave a nice day!"
Async functions
Async functions handle asynchronous operations, allowing them to pause and wait for the operation to complete before continuing:
Future<String> fetchData() async {
// Simulate network call
await Future.delayed(Duration(seconds: 2));
return "Data from server";
}
fetchData().then((data) => print(data)); // Prints "Data from server" after 2 seconds
Arrow syntax
Arrow syntax provides a concise way to define functions:
String greet(String name) => "Hello, $name!";
int add(int x, int y) => x + y;
print(greet("Alice")); // Prints "Hello, Alice!"
print(add(5, 3)); // Prints 8
Control Flow in Dart
Control flow statements allow your Dart code to change its execution path based on certain conditions. Here are some common control flow constructs:
If Statements
Use if
statements to execute code blocks conditionally:
int number = 10;
if (number > 0) {
print("The number is positive.");
} else {
print("The number is non-positive.");
}
If-else if:
String grade = "A";
if (grade == "A") {
print("Excellent!");
} else if (grade == "B") {
print("Good job!");
} else {
print("Keep practicing!");
}
Switch Statements
Use switch
statements to efficiently handle multiple conditions:
String day = "Monday";
switch (day) {
case "Monday":
print("Start of the week.");
break;
case "Friday":
print("Almost weekend!");
break;
default:
print("Just another day in the week.");
}
Guards
Guards are a concise way to express conditions and execute code blocks only if those conditions are true. They are often used within conditional expressions:
int age = 20;
String message = age >= 18 ? "You are an adult." : "You are not an adult.";
print(message); // Prints "You are an adult."
Guards can improve code readability and maintainability, especially when dealing with simple conditions.
Remember: Control flow statements are essential for building complex logic in your Dart programs. Choose the appropriate construct based on the specific decisions you need to make within your code.
Loops in Dart
Loops allow you to execute a block of code repeatedly until a certain condition is met. Here are the common loop types in Dart:
for loop
- Used to iterate over a collection of elements (like lists or strings) or a specific number of times.
- Syntax:
for (int i = 0; i < 5; i++) {
// Code to be executed for each iteration
print(i);
}
- This loop iterates 5 times, printing the value of
i
in each iteration.
while and do-while loops
- while loop: Executes the code block as long as a condition is true.
- Syntax:
bool isRunning = true;
while (isRunning) {
// Code to be executed as long as isRunning is true
print("Running...");
isRunning = false; // Update condition to stop the loop
}
- do-while loop: Executes the code block at least once, then continues as long as a condition is true.
- Syntax:
int count = 0;
do {
// Code to be executed at least once
print(count);
count++;
} while (count < 3);
This loop prints 0, 1, and 2, even though the condition count < 3
becomes false after the first iteration.
break and continue
- break: Exits the loop immediately, regardless of the condition.
- continue: Skips the remaining code in the current iteration and jumps to the next iteration.
for (int i = 0; i < 5; i++) {
if (i == 3) {
break; // Exit after the third iteration
}
print(i);
}
for (int i = 0; i < 5; i++) {
if (i % 2 == 0) {
continue; // Skip even numbers
}
print(i);
}
The first loop prints 0, 1, and 2, then exits because of the break
statement. The second loop prints only odd numbers (1 and 3) due to the continue
statement.
Choosing the right loop type depends on the specific needs of your code.
Collections
Collections are groups of elements that can be accessed and manipulated in various ways. Dart offers several built-in collection types:
Lists
- Ordered collections of elements, allowing duplicates.
- Use square brackets
[]
for creation and access.
List<String> colors = ["red", "green", "blue"];
print(colors[1]); // Prints "green"
colors.add("yellow"); // Add element to the end
Sets
- Unordered collections of unique elements.
- Use curly braces
{}
for creation and check membership.
Set<int> numbers = {1, 2, 3, 2}; // Duplicates are removed
print(numbers.contains(4)); // Prints false
Maps
- Unordered collections of key-value pairs.
- Use curly braces
{}
with key-value pairs separated by colons.
Map<String, String> fruits = {"apple": "red", "banana": "yellow"};
print(fruits["apple"]); // Prints "red"
fruits["orange"] = "orange"; // Add key-value pair
Operators
Collections support various operators for common operations:
+
: Concatenates lists.in
: Checks if an element exists in a collection.length
: Returns the number of elements in a collection.isEmpty
: Checks if a collection is empty.forEach
: Iterates over elements and executes a function for each.
List<String> days = ["Mon", "Tue"];
List<String> weekend = ["Sat", "Sun"];
List<String> allDays = days + weekend; // Concatenation
for (String day in allDays) {
print(day); // Prints each day
}
Cascades
Cascades allow chaining method calls on the same object for a more readable flow:
User user = User(name: "Bob", age: 30)
..id = 123
..isActive = true;
print(user); // Prints User(name: Bob, age: 30, id: 123, isActive: true)
Classes
Classes are blueprints for creating objects that share similar properties and behaviors:
class User {
String name;
int age;
User({required this.name, required this.age});
void printInfo() {
print("Name: $name, Age: $age");
}
}
User alice = User(name: "Alice", age: 30);
alice.printInfo(); // Prints "Name: Alice, Age: 30"
Getters and setters
Getters and setters allow controlled access and modification of an object's properties:
class Person {
String _name; // Private field
String get name => _name; // Getter
set name(String newName) {
if (newName.length > 0) {
_name = newName;
} else {
print("Name cannot be empty");
}
} // Setter
}
Person john = Person();
john.name = "John Doe"; // Setter is called
print(john.name); // Getter is called, prints "John Doe"
this
keyword
The this
keyword refers to the current object instance:
class Point {
int x;
int y;
Point(this.x, this.y);
void move(int dx, int dy) {
this.x += dx;
this.y += dy;
}
}
Point origin = Point(0, 0);
origin.move(3, 5);
print(origin.x); // Prints 3
print(origin.y); // Prints 5
Named constructors
Named constructors provide alternative ways to create objects with different initialization options:
class User {
final String name;
final int age;
User.fromNameAge(this.name, this.age);
User.fromJson(Map<String, dynamic> json)
: name = json['name'] as String,
age = json['age'] as int;
}
User user1 = User.fromNameAge("Alice", 30);
User user2 = User.fromJson({"name": "Bob", "age": 25});
Factory constructors
Factory constructors offer more flexibility in object creation, potentially returning existing objects or creating new ones based on logic:
class Logger {
static final Logger _instance = Logger._internal();
factory Logger() {
return _instance;
}
Logger._internal();
void log(String message) {
print(message);
}
}
Logger logger = Logger();
logger.log("This is a log message.");
Mixins
Mixins allow classes to reuse functionality from other classes without inheritance:
mixin Flyable {
void fly() {
print("I'm flying!");
}
}
class Bird {
void chirp() {
print("Chirp!");
}
}
class Superman extends Bird with Flyable {
void useSuperpower() {
print("Using super power!");
}
}
Superman clark = Superman();
clark.chirp(); // From Bird
clark.fly(); // From Flyable
clark.useSuperpower(); // From Superman
Dart Command-Line Interface (CLI)
The Dart CLI (dart
) provides tools for various development tasks:
Running:
- Run Dart apps: Use
dart run
followed by the entry point script (e.g.,dart run bin/my_app.dart
). - Run Dart Pad code: Paste code directly in the Dart Pad web interface (https://dartpad.dev/).
Compiling:
- AOT compile: Use
dart compile
to compile Dart code to native machine code for faster execution at runtime. - JIT compile: By default, the Dart VM performs Just-In-Time (JIT) compilation, translating code to machine code as it's executed.
Transpiling to JavaScript:
- Use the
dart compile js
command to convert Dart code to equivalent JavaScript code that can run in web browsers.
Profiling:
- Use the
dart devtool isolate-inspect
command to launch a debugger and profile Dart applications to identify performance bottlenecks.
I/O in Dart
The dart:io
library provides functionalities for interacting with the operating system, including:
Printing
Use print
or stdout.write
to print output to the console:
print("Hello, world!"); // Prints "Hello, world!" to the console
stdout.write("This "); // Prints "This " without a newline
stdout.writeln("is on a new line."); // Prints "is on a new line." with a newline
Reading user input
Use stdin.readLineSync
to read a line of user input from the console:
String name = stdin.readLineSync()!; // Reads user input and stores it in "name"
print("Hello, $name!"); // Prints "Hello, [user input]!"
Note: Using !
after stdin.readLineSync
is only recommended when you are sure the user will provide input, as it throws an exception otherwise. Consider alternative approaches for more robust error handling.
Writing and reading files
Use the File
class to interact with files:
Writing to a file:
final file = File('output.txt');
file.writeAsString('This is content written to the file.');
Reading from a file:
final file = File('data.txt');
String contents = await file.readAsString();
print(contents); // Prints the content of the file
Remember: The dart:io
library can only be used in command-line applications and server-side code, not in web apps.
Futures
Futures represent the eventual result of an asynchronous operation in Dart. They are used to handle operations that take some time to complete, allowing your code to continue execution without waiting.
Key points about Futures:
- Creation: Functions can return
Future<T>
objects, indicating they perform an asynchronous operation and will eventually return a value of typeT
(or an error). - Waiting for completion: Use
await
to wait for a future to complete and obtain its result:
Future<String> fetchData() async {
// Simulate network call
await Future.delayed(Duration(seconds: 2));
return "Data from server";
}
String data = await fetchData();
print(data); // Prints "Data from server" after 2 seconds
- Error handling: Futures can complete with errors. Use
catchError
ortry...catch
blocks to handle them:
Future<String> readFile() async {
try {
final file = File('data.txt');
return await file.readAsString();
} catch (error) {
print("Error reading file: $error");
return "";
}
}
- Chaining operations: Use
then
orasync/await
to chain operations that depend on the result of a previous future:
Future<void> processData(String data) async {
print("Processing data: $data");
// Simulate processing
await Future.delayed(Duration(seconds: 1));
print("Data processed successfully.");
}
fetchData().then(processData); // Calls processData with data from fetchData
Futures provide a powerful mechanism for handling asynchronous operations in Dart, making your code more responsive and efficient.