← Articles

Introduction to Dart: a language built for client apps

By Mark · 11 March 20261 view

Dart was designed for building user interfaces. That design intent explains features that seem redundant with other languages — until you use them in a UI context.

Null safety

Null pointer exceptions are the most common crash in mobile apps. Dart's sound null safety makes null-related crashes a compile-time error rather than a runtime surprise:

String name = 'Alice';       // Non-nullable — guaranteed never null
String? nickname;            // Nullable — must be checked before use

final upper = nickname?.toUpperCase();   // Safe access — returns null if nickname is null
final display = nickname ?? name;        // Null-coalescing
nickname ??= 'Ali';                      // Null-aware assignment

Async/await for UI responsiveness

Network calls, database reads, and file access are all async. Dart's async/await makes sequential async code readable:

Future<void> loadDashboard() async {
  setState(() => isLoading = true);
  try {
    final user = await userRepo.getUser(userId);
    final orders = await orderRepo.getOrders(userId);
    setState(() {
      _user = user;
      _orders = orders;
      isLoading = false;
    });
  } catch (e) {
    setState(() { error = e.toString(); isLoading = false; });
  }
}

// Parallel fetches — both requests fire simultaneously
Future<void> loadDashboardFast() async {
  final results = await Future.wait([
    userRepo.getUser(userId),
    orderRepo.getOrders(userId),
  ]);
}

Isolates for background work

Dart is single-threaded within an isolate. CPU-heavy work on the main isolate blocks the UI thread and causes jank:

// Move JSON parsing off the UI thread
final products = await compute(parseProducts, rawJsonString);

List<Product> parseProducts(String json) {
  final list = jsonDecode(json) as List;
  return list.map((e) => Product.fromJson(e as Map<String, dynamic>)).toList();
}

// For longer-running work:
final result = await Isolate.run(() => expensiveComputation(data));

Extension methods

extension StringX on String {
  bool get isValidEmail =>
      RegExp(r'^[\w.-]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(this);

  String capitalize() =>
      isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';
}

'[email protected]'.isValidEmail  // true
'hello'.capitalize()              // 'Hello'

AOT vs JIT compilation

  • JIT (development): Dart VM interprets code — enables hot reload
  • AOT (release): compiles to native ARM — fast startup, no VM overhead

This dual-mode explains why Flutter hot reload is instant yet production builds perform like native apps.

Records and patterns (Dart 3)

// Records: lightweight anonymous tuples with named fields
(String name, int age) getUser() => ('Alice', 30);
final (name, age) = getUser();

// Exhaustive pattern matching
switch (shape) {
  case Circle(radius: final r): return math.pi * r * r;
  case Rectangle(width: final w, height: final h): return w * h;
}

Common pitfalls

Using dynamic to avoid type errors. dynamic opts out of type checking entirely. Mistakes become runtime crashes instead of compile-time errors. Use generics or sealed classes instead.

Blocking the event loop. Any synchronous work over ~2ms — large JSON parsing, image processing, sorting large lists — causes dropped frames on the UI thread. Always offload heavy computation to compute() or Isolate.run().

Sign in to like, dislike, or report.

Comments

No comments yet. Be the first!

Sign in to leave a comment.

Introduction to Dart: a language built for client apps — ANN Tech