Partimos en la cual, ya tenemos una app con funcionalidades interesantes, lo último fue, Instalar, configurar y usar fuentes tipográficas en Flutter, ahora, vamos a aprender un tema muy importante que son las buenas prácticas en nuestra app, los parones de diseño.
De manera simple, singleton asegura que una clase tenga solo una instancia en todas las aplicaciones y proporciona un punto de acceso global.
Una instancia u objeto de una Clase es una representación concreta y específica de una Clase
El patrón Singleton es un patrón de diseño de los muchos que existen que garantiza que una clase tenga una sola instancia; y es básicamente eso; para ello, se emplea la programación orientada a objetos. En Flutter, el patrón Singleton se utiliza a menudo en combinación con el diseño Material para proporcionar un acceso global a determinados widgets de Material que se utilizan en la aplicación.
Por ejemplo, un singleton DatabaseHelper puede utilizarse para proporcionar acceso global a una base de datos SQLite; esta es una implementación que comúnmente hago en el curso de Flutter y en mis aplicaciones; este tipo de adaptaciones, son comunes en Flutter y facilitan el proceso de creación de aplicaciones organizadas, modulares y escalables.
En general, la implementación de un patrón Singleton en Flutter sigue los mismos principios que en cualquier otro lenguaje de programación, garantizando que solo se cree una instancia de una determinada clase, y proporcionando un punto de acceso global para esa instancia.
Qué es el patrón Singleton en Flutter y por qué se usa
El Singleton es un patrón creacional que garantiza que una clase tenga una única instancia durante toda la vida de la aplicación y que esta instancia sea accesible desde cualquier parte.
La lógica detrás de tener una única instancia
Piensa en estas situaciones típicas:
- Guardar el estado del modo oscuro.
- Recordar el nombre del usuario una vez logueado.
- Acceder a un cliente HTTP global.
- Mantener una única conexión a SQLite.
En mis propias apps, más de una vez intenté manejar configuraciones simples usando shared_preferences, y pronto descubrí que replicar lógica en diferentes pantallas rápidamente ensuciaba el código. Un Singleton resuelve ese problema de raíz.
¿Cuándo usarlo?
Este patrón se usa cuando necesitamos solo una instancia de algo en nuestra aplicación. Por ejemplo, en el caso de un inicio de sesión de usuario en nuestra aplicación, necesitamos esta información en varias partes, pero sería demasiado costoso en términos de procesamiento tener la información de usuario para cada flujo. En este caso podemos crear un singleton de usuario y sería la misma instancia para todos los flujos.
Beneficios
- Acceso controlado a una única instancia de datos. Es decir, puedes tener un control estricto sobre cómo y cuándo acceden los clientes.
- La instancia se crea solo cuando se va a utilizar.
- Accesible desde cualquier parte, singleton es una mejora sobre las variables globales.
¿Cómo garantizamos que una Clase tiene solo una instancia?
Una solución es hacer que la misma Clase sea responsable de su única instancia. La Clase puede garantizar que no se pueda crear ninguna otra instancia y debe proporcionar una forma de acceder a la instancia.
Singleton en Flutter
Una forma de crear un singleton en Dart es usar constructores factory. Como ejemplo, vamos a crear un singleton de usuario que tendrá el nombre de usuario. Para ello, primero creamos una Clase normal llamada UserSingleton. Lo hacemos un singleton creando una variable privada _instancia de tipo UserSingleton. Luego creamos mediante un factory UserSingleton() que devuelve la instancia.
Finalmente, creamos el constructor UserSingleton._internal() el cual es llamado exactamente una vez, y como es privado solo puede ser llamado en esta Clase y también evitamos que sea instanciado fuera de aquí. Este constructor se puede utilizar para inicializar la lógica.
En el ejemplo que vamos a usar, también tenemos un método para actualizar el nombre de usuario.
Finalmente, el UserSingleton es el siguiente:
class UserSingleton {
static final UserSingleton _instance = UserSingleton._internal();
String userName = 'Alvaro';
// using a factory is important
// because it promises to return _an_ object of this type
// but it doesn't promise to make a new one.
factory UserSingleton() {
return _instance;
}
// This named constructor is the "real" constructor
// It'll be called exactly once, by the static property assignment above
// it's also private, so it can only be called in this class
UserSingleton._internal() {
// initialization logic
}
// rest of class as normal, for example:
void updateUserName(String name) {
userName = name;
}
}Aquí puedes ver otra implementación del Singleton para un helper de una base de datos:
class DatabaseHelper {
static DatabaseHelper _instance;
factory DatabaseHelper() => _instance ??= DatabaseHelper._();
DatabaseHelper._();
// aquí puedes agregar métodos y propiedades relacionados con la base de datos
}En este ejemplo, la clase DatabaseHelper tiene una propiedad de clase estática _instance que contiene la única instancia de la clase. Un constructor factory es utilizado para crear una nueva instancia de la clase si aún no existe, o para devolver la instancia existente si ya ha sido creada.
Recuerda que…
La utilización de constructores factory en Dart se utiliza para cuando no es necesario crear una instancia de la clase en sí misma o en las que se tiene una instancia de una subclase o de la misma clase,
Al utilizar el constructor factory, se puede controlar cómo se crea la instancia de la clase y, a menudo, se puede optimizar y reutilizar la misma instancia para llamadas posteriores. Esto puede ser útil en situaciones donde crear una nueva instancia de una clase puede ser costoso en términos de tiempo y recursos.
El constructor actual DatabaseHelper._() es privado, lo que significa que no puede ser llamado desde fuera de la clase, forzando la creación de una instancia a través del constructor de fábrica/factory.
Para utilizar DatabaseHelper en tu aplicación, simplemente llama al constructor de fábrica, como se muestra a continuación:
DatabaseHelper db = DatabaseHelper();De esta manera, solo se puede crear una instancia de la clase DatabaseHelper, y se puede acceder a ella en cualquier parte de la aplicación.
Cómo implementar un Singleton en Dart (formas recomendadas)
Dart facilita mucho este patrón gracias a los constructores factory y a los constructores privados.
Singleton con constructor factory
Esta es una de las formas más comunes y claras:
class Singleton {
static final Singleton _instance = Singleton._internal();
factory Singleton() {
return _instance;
}
Singleton._internal();
}Ventajas:
- Siempre devuelve la misma instancia.
- Control total sobre cuándo y cómo se inicializa.
En una ocasión, utilicé este mismo patrón para un DatabaseHelper y funcionó perfecto, incluso en apps con mucha navegación.
Singleton con static instance
Un patrón aún más explícito:
class Config {
Config._privateConstructor();
static final Config instance = Config._privateConstructor();
String apiUrl = 'https://api.example.com';
}Se usa así:
final config = Config.instance;Lazy vs eager initialization
Eager (instancia inmediata): útil si la clase es ligera.
Lazy (solo se crea cuando se necesita): ideal si la inicialización es costosa.
Ejemplo lazy:
class LazySingleton {
LazySingleton._();
static LazySingleton? _instance;
static LazySingleton get instance {
_instance ??= LazySingleton._();
return _instance!;
}
}Ejemplos prácticos de Singleton en Flutter
Estos son casos reales que suelo ver —y usar— en apps de producción.
Caso 1: Estado global simple
Cuando estaba probando patrones de diseño para enseñar en mis cursos, solía demostrar cómo un Singleton puede manejar algo tan simple como un texto global que cambia desde cualquier pantalla.
class AppState {
static final AppState _instance = AppState._internal();
String text = "Hola";
factory AppState() => _instance;
AppState._internal();
void updateText(String newText) => text = newText;
}Caso 2: SharedPreferences como Singleton
Este patrón es imprescindible cuando el estado debe persistir.
class UserPreferences {
static final UserPreferences _instance = UserPreferences._internal();
factory UserPreferences() => _instance;
UserPreferences._internal();
SharedPreferences? _prefs;
Future<void> initPrefs() async {
_prefs = await SharedPreferences.getInstance();
}
String get defaultRoute => _prefs?.getString('defaultRoute') ?? '/login';
set defaultRoute(String value) => _prefs?.setString('defaultRoute', value);
}Antes solía replicar llamadas a SharedPreferences en diversas pantallas; hacerlo Singleton limpió mi arquitectura por completo.
Caso 3: DatabaseHelper para SQLite
Este es uno de los casos que más uso en mis apps:
class DatabaseHelper {
static DatabaseHelper? _instance;
factory DatabaseHelper() => _instance ??= DatabaseHelper._();
DatabaseHelper._();
}Una sola instancia maneja toda la base de datos de forma eficiente.
Cuándo usar (y cuándo NO usar) un Singleton
✔ Cuándo usarlo
- Necesitas una única instancia compartida.
- La creación de la clase es costosa.
- Debes manejar configuraciones globales.
- Quieres evitar estados duplicados entre pantallas.
En mi propio trabajo, tener instancias repetidas del usuario en cada flujo me costó rendimiento hasta que adopté un Singleton.
✖ Cuándo NO usarlo
- Si puedes inyectar dependencias mejor con Provider, Riverpod o GetIt.
- Si necesitas varias instancias para testing.
- Si el estado no es verdaderamente global.
- El sobreuso del patrón puede acoplar demasiado los módulos.
Mejores prácticas para usar Singletons en Flutter
Testing, modularidad y mantenimiento
- Mantén la lógica dentro del Singleton lo más atómica posible.
- No abuses del acceso global para todo.
- Evita que el Singleton se convierta en una “clase dios”.
- Cómo evitar código acoplado
Usa interfaces si necesitas swap de instancias.
Limita el acceso escribiendo métodos específicos, no exponiendo todo.
Complementa con providers cuando haya más de un estado importante.
Preguntas frecuentes sobre Singleton en Flutter
- ¿Es malo usar Singletons?
No, lo malo es usarlos en exceso. Úsalos donde realmente tiene sentido. - ¿Qué diferencia hay entre factory y static instance?
El factory permite lógica adicional para decidir qué instancia devolver. Static instance es más directo. - ¿Es thread-safe en Flutter?
Sí, porque Dart garantiza inicialización única en zonas aisladas. - ¿Es mejor usar Provider o un Singleton?
Depende: Provider administra estado reactivo; Singleton, estado global no reactivo.
Conclusión
El patrón Singleton en Flutter es simple pero poderoso. A mí me salvó en múltiples ocasiones, especialmente cuando buscaba mantener mi arquitectura limpia mientras mis apps crecían. No es magia, pero bien aplicado te ahorra tiempo, errores y desorden.
Con ejemplos claros, variantes efectivas y buenas prácticas, tienes ahora una guía completa para decidir cuándo y cómo usarlo en tus proyectos.
Ahora, aprende a usar el patrón de Observer en Flutter.
Acepto recibir anuncios de interes sobre este Blog.
De manera simple, singleton asegura que una clase tenga solo una instancia en todas las aplicaciones y proporciona un punto de acceso global. Veremos como usarlo en Flutter.