Dart 3 Migration Guide

This guide covers the migration from older AngularDart versions to AngularDart Reborn (Dart 3). AngularDart Reborn requires Dart 3, so this migration is a prerequisite.

Overview of Changes

Dart 3 introduced significant changes:

  • Null safety - Types are non-nullable by default
  • Class modifiers - sealed, final, interface, base, mixin
  • Records - Anonymous immutable data structures
  • Patterns - Advanced pattern matching
  • Switch expressions - Expressive switch statements

Step 1: Update SDK Constraint

environment:
  sdk: '>=3.0.0 <4.0.0'

Step 2: Update Dependencies

Update all dependencies to versions compatible with Dart 3 and AngularDart 8:

dart pub upgrade
dart pub outdated

Step 3: Fix Type Errors

Implicit casts removed

Dart 3 requires explicit casts:

// Old
dynamic value = 'hello';
String s = value; // Implicit cast

// Dart 3
dynamic value = 'hello';
String s = value as String; // Explicit cast
// or
String s = value.toString();

Generic type inference

// Old
List list = []; // List<dynamic>

// Dart 3
List<String> list = []; // Must specify type
// or
var list = <String>[];

Step 4: Null Safety Migration

Making types nullable

Add ? to types that can be null:

// Before
String name;
List<User> users;

// After
String? name;
List<User>? users;

Required parameters

// Before
void greet(String name) { }

// After (if name can be null)
void greet(String? name) { }

// Or make it required with a default
void greet([String name = 'World']) { }

Late initialization

Use late for variables initialized after construction:

class MyComponent {
  late final UserService _userService;

  MyComponent(Injector injector) {
    _userService = injector.get(UserService);
  }
}

Null checks

Use ! to assert non-null:

String name = user!.name; // Assert user is not null

Use ?. for safe navigation:

String? name = user?.name; // Null if user is null

Step 5: Class Modifiers

Dart 3 introduced class modifiers. Update your classes:

// Abstract classes that were used as mixins
abstract mixin class MyMixin { }

// Classes that should not be extended or implemented
final class MyService { }

// Sealed class hierarchies
sealed class Result { }
class Success extends Result { }
class Failure extends Result { }

Step 6: Records and Patterns (New in Dart 3)

Records

Records are anonymous, immutable data structures:

// Define a record type
typedef Point = (double x, double y);

// Create a record
final p = (1.0, 2.0);
print(p.$1); // 1.0
print(p.$2); // 2.0

// Named fields
final person = (name: 'Alice', age: 30);
print(person.name); // Alice

Pattern Matching

// Switch with patterns
switch (shape) {
  case Circle(radius: var r) when r > 0:
    print('Valid circle with radius $r');
  case Square(side: var s):
    print('Square with side $s');
  case _:
    print('Unknown shape');
}

// If-case
if (result case Success(value: final v)) {
  print('Got value: $v');
}

Step 7: Update AngularDart Code

Component changes

// Before (AngularDart 5/6/7)
@Component(
  selector: 'my-app',
  template: '<h1>{{title}}</h1>',
)
class AppComponent {
  String title = 'Hello';
}

// After (Dart 3 / AngularDart 8)
@Component(
  selector: 'my-app',
  template: '<h1>{{title}}</h1>',
)
class AppComponent {
  String title = 'Hello';
}

Directive changes

// Before
@Directive(selector: '[tooltip]')
class TooltipDirective {
  String? text;

  @Input()
  set tooltip(String value) { text = value; }
}

// After
@Directive(selector: '[tooltip]')
class TooltipDirective {
  String? text;

  @Input()
  set tooltip(String? value) { text = value; }
}

Service changes

// Before
@Injectable()
class DataService {
  Future<List> getData() async {
    return [];
  }
}

// After
@Injectable()
class DataService {
  Future<List<Item>> getData() async {
    return <Item>[];
  }
}

Step 7: Update Tests

// Before
test('should work', () {
  var result = service.getData();
  expect(result, isNotNull);
});

// After
test('should work', () {
  var result = service.getData();
  expect(result, isNotNull);
  // Add explicit type checks if needed
  expect(result, isA<List<Item>>());
});

Common Issues

undefined_class errors

Ensure all imports are present and types are correctly spelled.

missing_return errors

Add explicit return statements or => null for nullable returns.

invalid_override errors

Ensure override signatures match the parent class exactly.

unused_element warnings

Remove unused private members or add // ignore: unused_element.

Resources