Skip to content

Best Practices

Code Style

Naming Conventions

dart
// Classes: UpperCamelCase
class UserProfile {}

// Variables, functions: lowerCamelCase
String userName = 'Alice';
void calculateTotal() {}

// Constants: lowerCamelCase
const maxRetries = 3;
const apiEndpoint = 'https://api.example.com';

// Private members: _leadingUnderscore
class BankAccount {
  double _balance;
}

// Libraries: lowercase_with_underscores
import 'package:my_app/user_service.dart';

Use Type Annotations

dart
// Good
String getName() => 'Alice';
List<int> getNumbers() => [1, 2, 3];

// Avoid
getName() => 'Alice';  // Type unclear

Prefer final over var

dart
// Good
final name = 'Alice';
final numbers = [1, 2, 3];

// Less preferred
var name = 'Alice';

Null Safety

dart
// Always handle nulls safely
String? getName() => null;

void main() {
  // Good
  String name = getName() ?? 'Guest';
  
  // Good
  String? maybeName = getName();
  if (maybeName != null) {
    print(maybeName.toUpperCase());
  }
  
  // Avoid
  // String name = getName()!;  // Risky!
}

Functions

Keep Functions Small

dart
// Good - single responsibility
bool isValidEmail(String email) {
  return email.contains('@') && email.contains('.');
}

bool isAdult(int age) {
  return age >= 18;
}

// Avoid - doing too much
void processUser(Map user) {
  // 100 lines of code...
}

Use Named Parameters

dart
// Good
void createUser({
  required String name,
  required String email,
  int age = 0,
}) {}

// Less clear
void createUser(String name, String email, int age) {}

Collections

Use Collection Literals

dart
// Good
var list = <int>[];
var map = <String, int>{};
var set = <String>{};

// Avoid
var list = List<int>();
var map = Map<String, int>();

Use Collection If/For

dart
// Good
var numbers = [
  1,
  2,
  if (includeThree) 3,
  for (var i in extras) i,
];

// Avoid
var numbers = [1, 2];
if (includeThree) numbers.add(3);
for (var i in extras) numbers.add(i);

Classes

Use Initializing Formals

dart
// Good
class Point {
  final double x, y;
  Point(this.x, this.y);
}

// Avoid
class Point {
  final double x, y;
  Point(double x, double y) {
    this.x = x;
    this.y = y;
  }
}

Prefer Composition Over Inheritance

dart
// Good
class Car {
  final Engine engine;
  Car(this.engine);
}

// Less flexible
class Car extends Engine {}

Async Programming

Always Use async/await

dart
// Good
Future<String> fetchData() async {
  var response = await http.get(url);
  return response.body;
}

// Avoid
Future<String> fetchData() {
  return http.get(url).then((response) {
    return response.body;
  });
}

Handle Errors

dart
Future<void> loadData() async {
  try {
    var data = await fetchData();
    processData(data);
  } catch (e) {
    print('Error: $e');
  }
}

Performance

Use const Constructors

dart
// Good - created once
const icon = Icon(Icons.home);

// Less efficient - created every time
final icon = Icon(Icons.home);

Avoid Unnecessary Rebuilds

dart
// Good - extract to const
class MyWidget extends StatelessWidget {
  static const title = Text('Hello');
  
  @override
  Widget build(BuildContext context) {
    return title;
  }
}

Documentation

Document Public APIs

dart
/// Calculates the area of a circle.
///
/// The [radius] must be positive.
/// Returns the area as a double.
double calculateCircleArea(double radius) {
  assert(radius > 0, 'Radius must be positive');
  return 3.14159 * radius * radius;
}

Testing

Write Tests

dart
import 'package:test/test.dart';

void main() {
  test('addition works', () {
    expect(2 + 2, equals(4));
  });
  
  test('string contains substring', () {
    expect('Hello, World', contains('World'));
  });
}

Summary

  1. Follow naming conventions
  2. Use type annotations for clarity
  3. Embrace null safety
  4. Keep functions small and focused
  5. Use named parameters for clarity
  6. Prefer const and final
  7. Handle errors properly
  8. Document public APIs
  9. Write tests
  10. Use linter (analysis_options.yaml)

Next Steps

Content is for learning and research only.