Operators de Transformación
Los operators de transformación te permiten convertir valores de un tipo a otro o reaccionar solo a cambios específicos de propiedades.
map()
Transforma cada valor usando una función. El ValueListenable mapeado se actualiza cada vez que la fuente cambia.
Uso Básico
void main() {
final source = ValueNotifier<String>('hello');
final upperCaseSource = source.map((s) => s.toUpperCase());
print(upperCaseSource.value); // Prints: HELLO
source.value = 'world';
print(upperCaseSource.value); // Prints: WORLD
}Transformación de Tipo
Puedes cambiar el tipo proporcionando un parámetro de tipo:
final intNotifier = ValueNotifier<int>(42);
// Transformación de tipo explícita
final stringNotifier = intNotifier.map<String>((i) => 'Value: $i');
// El tipo se infiere como ValueListenable<String>
print(stringNotifier.value); // "Value: 42"Casos de Uso Comunes
Formatear Valores para Mostrar
import 'package:intl/intl.dart';
final priceNotifier = ValueNotifier<double>(19.99);
final formatter = NumberFormat.currency(symbol: '\$');
final formattedPrice = priceNotifier.map((price) => formatter.format(price));
ValueListenableBuilder<String>(
valueListenable: formattedPrice,
builder: (context, price, _) => Text(price), // "$19.99"
);Extraer Propiedades Anidadas
final userNotifier = ValueNotifier<User>(user);
final userName = userNotifier.map((user) => user.name);
final userEmail = userNotifier.map((user) => user.email);Transformaciones Complejas
final dataNotifier = ValueNotifier<RawData>(data);
final processed = dataNotifier.map((raw) {
return ProcessedData(
value: raw.value * 2,
formatted: raw.toString().toUpperCase(),
timestamp: DateTime.now(),
);
});Cuándo Usar map()
Usa map() cuando:
- ✅ Necesites transformar cada valor
- ✅ Necesites cambiar el tipo
- ✅ La transformación siempre sea válida
- ✅ Quieras ser notificado en cada cambio de la fuente
Rendimiento
La función de transformación se llama en cada cambio de valor de la fuente. Para transformaciones costosas, considera usar select() si solo necesitas reaccionar a cambios de propiedades específicas.
select()
Reacciona solo cuando una propiedad seleccionada del valor cambia. Esto es más eficiente que map() cuando solo te importan propiedades específicas de un objeto complejo.
Uso Básico
void main() {
final notifier = ValueNotifier(User(age: 18, name: "John"));
// Only notifies when age changes
final birthdayNotifier = notifier.select<int>((model) => model.age);
birthdayNotifier.listen((age, _) => print('Age changed to: $age'));
print('Initial age: ${birthdayNotifier.value}'); // 18
// This triggers the listener (age changed)
notifier.value = User(age: 19, name: "John");
// Prints: Age changed to: 19
// This does NOT trigger the listener (age unchanged)
notifier.value = User(age: 19, name: "Johnny");
// No output - name changed but age stayed the same
// This triggers the listener (age changed)
notifier.value = User(age: 20, name: "Johnny");
// Prints: Age changed to: 20
}Cómo Funciona
La función de selector se llama en cada cambio de valor, pero el resultado solo se propaga cuando es diferente del resultado anterior (usando comparación ==).
final userNotifier = ValueNotifier<User>(User(age: 18, name: "John"));
final ageNotifier = userNotifier.select<int>((u) => u.age);
ageNotifier.listen((age, _) => print('Age: $age'));
userNotifier.value = User(age: 18, name: "Johnny");
// Sin salida - la edad no cambió
userNotifier.value = User(age: 19, name: "Johnny");
// Imprime: Age: 19Casos de Uso Comunes
Rastrear Propiedades Específicas del Modelo
class AppState {
final bool isLoading;
final String? error;
final List<Item> items;
AppState({required this.isLoading, this.error, required this.items});
}
final appState = ValueNotifier<AppState>(initialState);
// Solo reconstruir cuando cambia el estado de carga
final isLoading = appState.select<bool>((state) => state.isLoading);
// Solo reconstruir cuando cambia el error
final error = appState.select<String?>((state) => state.error);
// Solo reconstruir cuando cambia el conteo de items
final itemCount = appState.select<int>((state) => state.items.length);Evitar Reconstrucciones Innecesarias
class Settings {
final String theme;
final String language;
final bool notifications;
Settings({required this.theme, required this.language, required this.notifications});
}
final settings = ValueNotifier<Settings>(defaultSettings);
// El widget solo se reconstruye cuando cambia el tema, no cuando cambian language o notifications
final theme = settings.select<String>((s) => s.theme);
ValueListenableBuilder<String>(
valueListenable: theme,
builder: (context, theme, _) => ThemedWidget(theme: theme),
);Seleccionar Propiedades Computadas
class ShoppingCart {
final List<Item> items;
ShoppingCart(this.items);
double get total => items.fold(0.0, (sum, item) => sum + item.price);
}
class Item {
final double price;
Item(this.price);
}
final cart = ValueNotifier<ShoppingCart>(ShoppingCart([]));
// Solo notificar cuando cambia el total
final total = cart.select<double>((c) => c.total);map() vs select()
| Característica | map() | select() |
|---|---|---|
| Notifica cuando | La fuente cambia | El valor seleccionado cambia |
| Usar para | Transformar siempre todos los cambios | Reaccionar solo a propiedades específicas |
| Rendimiento | Cada cambio de fuente | Solo cuando el valor seleccionado difiere |
| Cambio de tipo | Sí | Sí |
final user = ValueNotifier<User>(User(age: 18, name: "John"));
// map() - notifica en CADA cambio de usuario
final userMap = user.map((u) => u.age);
user.value = User(age: 18, name: "Johnny"); // ✅ Notifica (edad todavía 18)
// select() - notifica solo cuando la edad REALMENTE cambia
final userSelect = user.select<int>((u) => u.age);
user.value = User(age: 18, name: "Johnny"); // ❌️ Sin notificación (edad sin cambios)Cuándo Usar select()
Usa select() cuando:
- ✅ Solo te importan propiedades específicas de un objeto complejo
- ✅ Quieras evitar notificaciones innecesarias
- ✅ El objeto cambia frecuentemente pero la propiedad que te importa no
- ✅ Quieras optimizar las reconstrucciones de widgets
Mejor Práctica
select() es ideal para view models u objetos de estado que tienen muchas propiedades pero tu widget solo depende de algunas de ellas.
Encadenando Transformaciones
Puedes encadenar map() y select() con otros operators:
final user = ValueNotifier<User>(user);
user
.select<int>((u) => u.age) // Solo cuando la edad cambia
.where((age) => age >= 18) // Solo adultos
.map<String>((age) => 'Age: $age') // Formatear para mostrar
.listen((text, _) => print(text));