Why Learn Dart Deeply?
Variables Done Right
Use final by Default
final name = 'Nisarg';
// name = 'Flutter'; // ❌ Error
var count = 0;
count++; // ✅ fine, it changesfinal vs const
final currentTime = DateTime.now(); // ✅ runtime
const pi = 3.14159; // ✅ compile-time
// const currentTime = DateTime.now(); // ❌ ErrorUse const Constructors Whenever Possible
class User {
final String name;
const User(this.name);
}
const user = User('Nisarg');Flashcards
final vs const — what's the real difference?
Hint: Think 'when is the value known?'
Tap to flipfinal: assigned once, value resolved at runtime. const: value must be known at compile time.
Null Safety, the Idiomatic Way
Use Null-Aware Operators
print(user?.name);
final name = user?.name ?? 'Guest';
String? name;
name ??= 'Guest';Building Collections Declaratively
Collection if
final items = [
'Home',
if (isLoggedIn) 'Profile',
];Collection for
final widgets = [
for (final user in users)
if (user.isActive)
Text(user.name),
];The Spread Operator ...
final numbers = [1, 2, 3];
final moreNumbers = [0, ...numbers, 4, 5];
// [0, 1, 2, 3, 4, 5]
final items = [
'Home',
...?menuItems,
];Records & Pattern Matching
Destructuring with Records
(String, int) getUser() {
return ('Nisarg', 26);
}
final (name, age) = getUser();
({String name, int age}) getUser() {
return (name: 'Nisarg', age: 26);
}
final (:name, :age) = getUser();Records for Multiple Return Values
(User user, bool isNewUser) createUser() {
return (user, true);
}
final (user, isNewUser) = createUser();Pattern Matching in switch
switch (value) {
case 0:
print('Zero');
case 1:
print('One');
case > 1:
print('Positive');
case < 0:
print('Negative');
}Prefer switch Expressions
String getStatusText(Status status) => switch (status) {
Status.loading => 'Loading',
Status.success => 'Success',
Status.error => 'Error',
};Guards with when
switch (age) {
case >= 18 when age < 60:
print('Adult');
case >= 60:
print('Senior');
default:
print('Minor');
}Flashcards
What is a Dart record?
Tap to flipA lightweight, anonymous, immutable data structure that can hold multiple values — positional or named — without declaring a class.
Modeling State with Sealed Classes
Sealed Classes for State Management
sealed class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final User user;
UserLoaded(this.user);
}
class UserError extends UserState {
final String message;
UserError(this.message);
}switch (state) {
case UserInitial():
return const Text('Start');
case UserLoading():
return const CircularProgressIndicator();
case UserLoaded(:final user):
return Text(user.name);
case UserError(:final message):
return Text(message);
}Flashcards
Why use a sealed class for UI state instead of nullable booleans?
Hint: Think 'unrepresentable invalid states'
Tap to flipIt makes invalid state combinations impossible to represent, and the compiler enforces exhaustive handling in every switch.
Extensions & Extension Types
Extension Methods
extension StringX on String {
bool get isEmail => contains('@');
}
final isValid = email.isEmail;
extension IterableX<T> on Iterable<T> {
T? get firstOrNull => isEmpty ? null : first;
}Extension Types, Carefully
extension type UserId(String value) {}
final id = UserId('user_123');Expressive Syntax: Cascades
Cascade Notation ..
final user = User()
..name = 'Nisarg'
..email = 'test@example.com'
..age = 26;
final buffer = StringBuffer()
..write('Hello ')
..write('Flutter');Mastering Collections
map, where, and expand
final numbers = [1, 2, 3];
final doubled = numbers.map((n) => n * 2); // [2, 4, 6]
final evenNumbers = numbers.where((n) => n.isEven); // [2]
final values = [[1, 2], [3, 4]];
final result = values.expand((item) => item); // [1, 2, 3, 4]Iterable Is Lazy
final result = numbers
.where((n) => n.isEven)
.map((n) => n * 2);
// nothing has run yet
print(result.toList()); // now it runssync* for Lazy Iteration
Iterable<int> generateNumbers() sync* {
for (var i = 0; i < 10; i++) {
yield i;
}
}
Stream<int> generateNumbersAsync() async* {
for (var i = 0; i < 10; i++) {
await Future.delayed(const Duration(seconds: 1));
yield i;
}
}Async Done Right
Future.wait for Concurrency
final results = await Future.wait([
getUser(),
getProducts(),
getNotifications(),
]);
final (user, products, notifications) = await (
getUser(), getProducts(), getNotifications(),
).wait;late, Used Carefully
late final User user;
// ...
user = await getUser();factory Constructors
class User {
final String name;
User._(this.name);
factory User.fromJson(Map<String, dynamic> json) {
return User._(json['name'] as String);
}
}Don't Block the Event Loop
final result = await compute(heavyCalculation, input);Flashcards
When should you reach for compute() / an isolate instead of async/await?
Tap to flipasync/await is for I/O-bound work (network, files). Isolates are for CPU-bound work heavy enough to block the UI thread.
API Design That's Hard to Misuse
Named Parameters
class User {
final String name;
final String email;
final int age;
const User({
required this.name,
required this.email,
required this.age,
});
}
User(name: 'Nisarg', email: 'test@example.com', age: 26);Immutable Models + copyWith
class User {
final String name;
final int age;
const User({required this.name, required this.age});
User copyWith({String? name, int? age}) {
return User(
name: name ?? this.name,
age: age ?? this.age,
);
}
}
final updatedUser = user.copyWith(name: 'New Name');Private Library Members
Specific Exception Handling
try {
await fetchData();
} on TimeoutException {
// Handle timeout
} on SocketException {
// Handle network issue
} catch (e, stackTrace) {
// Handle unexpected error
}Never for Functions That Never Return
Never throwError(String message) {
throw Exception(message);
}Enums with Associated Data
enum ApiEnvironment {
development('https://dev.example.com'),
production('https://api.example.com');
final String baseUrl;
const ApiEnvironment(this.baseUrl);
}
final url = ApiEnvironment.production.baseUrl;typedef for Complex Signatures
typedef UpdateUserCallback = Future<void> Function(
String id,
Map<String, dynamic> data,
);
final UpdateUserCallback onUpdate;assert for Developer Errors
class User {
User(this.age) : assert(age >= 0);
final int age;
}Small, Composable Functions
Future<void> processUser(User user) async {
validateUser(user);
final transformedUser = transformUser(user);
await saveUser(transformedUser);
await notifyUser(transformedUser);
}Final Takeaways
Final review
Test what you learned across the whole post.
Which keyword must a variable's value satisfy to be known at compile time?
Flashcards
Collection if / for
Tap to flipSyntax for conditionally or programmatically building list/set/map literals inline, without a separate loop and .add() calls.
