Skip to content

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

dart
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:

dart
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
dart
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
dart
final userNotifier = ValueNotifier<User>(user);

final userName = userNotifier.map((user) => user.name);
final userEmail = userNotifier.map((user) => user.email);
Transformaciones Complejas
dart
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

dart
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 ==).

dart
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: 19

Casos de Uso Comunes

Rastrear Propiedades Específicas del Modelo
dart
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
dart
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
dart
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ísticamap()select()
Notifica cuandoLa fuente cambiaEl valor seleccionado cambia
Usar paraTransformar siempre todos los cambiosReaccionar solo a propiedades específicas
RendimientoCada cambio de fuenteSolo cuando el valor seleccionado difiere
Cambio de tipo
dart
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:

dart
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));

Próximos Pasos

Publicado bajo la Licencia MIT.