Best Practices
## Learning Objectives
- Follow Dart style guide
- Write clean, maintainable code
- Master effective patterns
- Learn Flutter-specific practices
## Naming Conventions
### Variables and Functions
```dart
// Variables: camelCase
var userName = 'Alice';
var isActive = true;
var itemCount = 42;
// Functions: camelCase
void fetchUserData() {}
int calculateTotal() {}
// Private members: leading underscore
class User {
String _privateField = '';
void _privateMethod() {}
}
```
### Classes and Types
```dart
// Classes: PascalCase
class UserAccount {}
class HttpClient {}
class ApiResponse {}
// Enums: PascalCase
enum Status { pending, active, completed }
enum Color { red, green, blue }
// Type aliases: PascalCase
typedef UserId = String;
typedef Callback = void Function();
```
### Constants
```dart
// Compile-time constants: PascalCase
const int maxItems = 100;
const String appName = 'MyApp';
// Enum values: camelCase (but keep enum PascalCase)
enum Status {
isPending,
isActive,
isCompleted,
}
```
### Files
```dart
// lowercase_with_underscores.dart
// user_repository.dart
// api_client.dart
// constants.dart
```
## Code Organization
### File Structure
```dart
// person.dart
class Person {
String name;
int age;
Person(this.name, this.age);
void greet() => print('Hello, I am $name');
}
```
### Import Organization
```dart
// Order: SDK, package, relative
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import '../models/user.dart';
import 'constants.dart';
```
## Variables
### Prefer var for Local Variables
```dart
// Good: type is clear from right side
var name = 'Alice';
var count = 0;
var items = ['a', 'b', 'c'];
// Acceptable: explicit when type isn't obvious
var points = [];
var mapping = {};
```
### Final When Possible
```dart
// Use final for values that don't change
final name = 'Alice';
final items = ['a', 'b', 'c'];
// Use const for compile-time constants
const maxRetries = 3;
const defaultTimeout = Duration(seconds: 30);
```
### Avoid var for Public Fields
```dart
// Good: explicit type for public API
class User {
final String name;
final int age;
User(this.name, this.age);
}
// Acceptable: var for local variables with clear context
var result = computeResult();
```
## Functions
### Prefer Arrow Functions for Short Functions
```dart
// Good
int square(int x) => x * x;
String capitalize(String s) => s.isEmpty ? '' : s[0].toUpperCase() + s.substring(1);
// Avoid for complex logic
void process() {
// Complex logic here
// Multiple statements
// Don't use arrow for multi-line
}
```
### Use Void for Side Effects Only
```dart
// Good: returns value
Future fetchUser(String id) async { ... }
// Good: void for side effects only
Future saveUser(User user) async { ... }
// Avoid: returning value but declared void
// void process() => computeResult(); // Confusing
```
## Classes
### Prefer Constructors with Initializer List
```dart
class Point {
final double x;
final double y;
Point(this.x, this.y); // Good
// Avoid: unnecessary named parameters for simple cases
// Point({required this.x, required this.y}); // Overkill here
}
```
### Use Named Constructors for Clarity
```dart
class Date {
int year;
int month;
int day;
Date(this.year, this.month, this.day);
// Named constructors for clarity
Date.today() : this(DateTime.now().year, DateTime.now().month, DateTime.now().day);
Date.fromJson(Map json) : this(json['year'], json['month'], json['day']);
}
```
### Private Fields for Encapsulation
```dart
class BankAccount {
String _accountNumber; // Private
double _balance; // Private
BankAccount(this._accountNumber, this._balance);
double get balance => _balance; // Read-only access
void deposit(double amount) {
if (amount > 0) _balance += amount;
}
}
```
## Null Safety
### Avoid Force Unwrap (!)
```dart
// Bad
String name = getName()!;
// Good: handle null explicitly
String? name = getName();
String displayName = name ?? 'Unknown';
```
### Prefer Late Sparingly
```dart
// Good: when lazy initialization is needed
class DataService {
late String data; // Defer initialization
Future load() async {
data = await fetchData();
}
}
// Better: if possible, initialize in constructor
class User {
final String name;
User(this.name);
}
```
## Collections
### Prefer Collection Literals
```dart
// Good
var list = [1, 2, 3];
var map = {'a': 1, 'b': 2};
var set = {1, 2, 3};
// Avoid: List<>() constructor
// var list = List.of([1, 2, 3]); // Verbose
```
### Use Spread Operators
```dart
var base = [1, 2, 3];
var combined = [0, ...base, 4]; // [0, 1, 2, 3, 4]
var defaults = {'a': 1, 'b': 2};
var overrides = {'b': 3, 'c': 4};
var config = {...defaults, ...overrides};
```
### Collection Conditionals
```dart
var isPremium = true;
var items = [
'Home',
if (isPremium) 'Premium Feature',
'About',
];
var filtered = [1, 2, 3, 4, 5]
.where((n) => n % 2 == 0)
.map((n) => n * 2)
.toList();
```
## Async Programming
### async/await Over Futures
```dart
// Good: clear async flow
Future loadData() async {
try {
var user = await fetchUser();
var posts = await fetchPosts(user.id);
print('Posts: $posts');
} catch (e) {
print('Error: $e');
}
}
// Avoid: excessive then chaining
// fetchUser().then((user) => fetchPosts(user.id)).then(...);
```
### Cancel Unused Futures
```dart
class MyWidget extends StatefulWidget {
@override
State createState() => _MyWidgetState();
}
class _MyWidgetState extends State {
Future? _future;
@override
void initState() {
super.initState();
_future = loadData();
}
@override
void dispose() {
_future = null; // Help garbage collection
super.dispose();
}
}
```
## Error Handling
### Specific Exceptions
```dart
try {
await fetchData();
} on NetworkException {
// Handle network errors
showNetworkError();
} on ServerException catch (e) {
// Handle server errors with context
showServerError(e.message);
} catch (e, stack) {
// Unexpected errors
reportError(e, stack);
}
```
### Result Pattern
```dart
class Result {
final T? data;
final String? error;
Result.success(this.data) : error = null;
Result.failure(this.error) : data = null;
bool get isSuccess => data != null;
}
Future> fetchUser() async {
try {
var user = await _api.getUser();
return Result.success(user);
} catch (e) {
return Result.failure(e.toString());
}
}
```
## Flutter-Specific
### StatelessWidget vs StatefulWidget
```dart
// Use StatelessWidget when possible
class Counter extends StatelessWidget {
final int count;
const Counter({required this.count});
@override
Widget build(BuildContext context) {
return Text('Count: $count');
}
}
```
### Const Widgets
```dart
// Good: const when possible
return const Text('Hello');
// Good: const constructor
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
return const Text('Hello');
}
}
```
### Avoid setState When Possible
```dart
// Good: derive from existing state
Text('Count: $props.count');
// Consider: Riverpod, Provider, Bloc for complex state
```
## Documentation
### Use Doc Comments
```dart
/// Calculates the sum of two numbers.
///
/// [a] The first number
/// [b] The second number
///
/// Returns the sum of a and b
int sum(int a, int b) => a + b;
```
## Summary
- Follow naming: camelCase (vars), PascalCase (types)
- Use final for immutable, const for compile-time
- Prefer var for local variables with clear types
- Use const constructors in Flutter
- Handle null safely: prefer `??` over `!`
- Use async/await over Future.then
- Be specific with exceptions
- Document public APIs with doc comments
- Keep files short and focused
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →