Singleton and Design Patterns in Dart & Flutter

Singleton and Design Patterns in Dart & Flutter

TB

Teqani Blogs

Writer at Teqani

November 7, 20254 min read

This article explores the application of the Singleton pattern and various design patterns in Dart and Flutter for building robust and maintainable applications. Understanding these patterns is crucial for creating scalable and testable codebases.

Singleton Pattern

The Singleton pattern ensures that a class has only one instance throughout the application lifecycle and provides a global point of access to it. This is commonly used for managing resources like database connections, shared preferences, or configuration settings.

Example:

class DatabaseService {
 DatabaseService._internal();
 static final DatabaseService _instance = DatabaseService._internal();
 factory DatabaseService() => _instance;
 void connect() => print("Database connected");
}

final db = DatabaseService();
db.connect(); // Always refers to the same instance

When to use:

  • Database connections
  • Shared preferences or local caches
  • Logging or configuration services

Common Design Patterns in Flutter

Factory Pattern

The Factory pattern creates objects without exposing the creation logic to the client. This is useful for abstracting the object creation process and providing flexibility in choosing which concrete class to instantiate.

class InvoiceFactory {
 static Invoice create(String type) {
 if (type == "gst") return GstInvoice();
 if (type == "retail") return RetailInvoice();
 throw Exception("Invalid type");
 }
}

Use Case: Widget factories, model parsing from JSON, or dependency creation.

Provider Pattern (State Management)

The Provider pattern, often used for state management in Flutter, implements the Observer Pattern. Widgets listen to changes in a provider and rebuild automatically. This allows for reactive UI updates and efficient state management.

class LoginProvider extends ChangeNotifier {
 bool isLoading = false;
 void toggleLoading() {
 isLoading = !isLoading;
 notifyListeners();
 }
}

Use Case: Dynamic UI updates (e.g., login progress, dashboard refresh).

Repository Pattern

The Repository pattern separates data access from business logic. This pattern provides an abstraction layer between your application and the data source, making it easier to switch between different data sources (e.g., APIs, databases, local caches) without affecting the rest of the application.

class InvoiceRepository {
 final ApiService api;
 InvoiceRepository(this.api);
 Future<List<Invoice>> fetchInvoices() => api.getInvoices();
}

Use Case: Clean architecture—makes APIs, DBs, and local caches swappable.

Builder Pattern

The Builder pattern is used when an object requires step-by-step construction. This pattern is useful for creating complex objects with many optional parameters or configurations. It allows you to construct an object incrementally.

class InvoiceBuilder {
 String? id;
 double? amount;
 InvoiceBuilder setId(String id) { this.id = id; return this; }
 InvoiceBuilder setAmount(double amount) { this.amount = amount; return this; }
 Invoice build() => Invoice(id!, amount!);
}

Use Case: Building complex objects like form submissions or configurations.

Command Pattern

The Command pattern encapsulates actions as objects, making them reusable and undoable. This pattern is useful for implementing features like undo/redo, queuing tasks, or managing button actions.

abstract class Command {
 void execute();
}

class PrintCommand implements Command {
 @override
 void execute() => print("Printing Invoice...");
}

Use Case: Button actions, undo/redo, queued tasks.

Summary Table

PatternCore IdeaCommon Use Case
SingletonOne shared instanceDB, config, logging
FactoryCentralized object creationAPI parsing, widget factories
Provider (Observer)Reactive state updatesUI state management
RepositoryAbstract data layerClean architecture
BuilderStepwise object creationForms, configurations
CommandEncapsulate actionsUndo/redo, task queues

In production Flutter apps, combining Singleton for services, Provider for state, and Repository for data separation gives a clean, testable, and scalable architecture.

TB

Teqani Blogs

Verified
Writer at Teqani

Senior Software Engineer with 10 years of experience

November 7, 2025
Teqani Certified

All blogs are certified by our company and reviewed by our specialists
Issue Number: #4778d9ea-a663-40e5-b344-e3369d52d5c9