How to Build a Singleton in Dart and Flutter

A Singleton ensures only one instance of a class exists in your app.
It’s useful for shared services like logging, analytics, or database access.
In Flutter, you can build a singleton using Dart’s language features without extra packages.

Approach A: Simple static instance

The easiest way is to create a static instance inside the class.

class AppLogger {
  static final AppLogger _instance = AppLogger._internal();
  factory AppLogger() => _instance;

  AppLogger._internal(); // Private constructor

  void log(String message) {
    print('LOG: $message');
  }
}

void main() {
  final logger1 = AppLogger();
  final logger2 = AppLogger();

  print(identical(logger1, logger2)); // true ✅
}
  • factory returns the same _instance each time.
  • _internal() keeps external code from creating new instances.

Approach B: Lazy initialization

If the object is expensive to create, initialize it only when used.

class DatabaseService {
  static DatabaseService? _instance;
  DatabaseService._();

  static DatabaseService get instance {
    _instance ??= DatabaseService._();
    return _instance!;
  }

  void connect() => print('Connected to DB');
}

void main() {
  DatabaseService.instance.connect(); // ✅ Only created when needed
}

This pattern avoids unnecessary memory allocation until the instance is accessed.


Approach C: Using top-level final

For simple global services, you can use a top-level final variable.

final config = Config();

class Config {
  final String apiUrl = 'https://api.example.com';
}

No need for constructors or factory methods — Dart ensures one instance per isolate.


Approach D: Singleton with Flutter’s Provider

In Flutter apps, use state management to inject singletons cleanly.

final analytics = AnalyticsService();

void main() {
  runApp(
    Provider<AnalyticsService>.value(
      value: analytics,
      child: const MyApp(),
    ),
  );
}

class AnalyticsService {
  void logEvent(String event) => print('Event: $event');
}

This makes the singleton accessible throughout the widget tree.


Decision Guide

  • For pure Dart services: Use the factory + static instance approach.
  • For lazy loading: Use late initialization (_instance ??=).
  • For Flutter apps: Provide the singleton using Provider or GetIt.
  • For simple configs: Use top-level constants or finals.

Recommendation

  • Prefer Dart’s built-in singleton patterns over external libraries.
  • Use dependency injection (Provider, Riverpod, or GetIt) for testable and scalable Flutter apps.
  • Keep singletons stateless when possible to avoid hidden global state.



Leave a Reply

Your email address will not be published. Required fields are marked *