Skip to content

Operators de Combinación

Los operators de combinación te permiten fusionar múltiples ValueListenables en un único observable, actualizándose cuando cualquier fuente cambia.

combineLatest()

Combina dos ValueListenables usando una función combinadora. El resultado se actualiza cuando cualquiera de las fuentes cambia.

Uso Básico

dart
void main() {
  final isLoadingData = ValueNotifier<bool>(false);
  final isLoadingUser = ValueNotifier<bool>(false);

  // Combine two loading states - true if either is loading
  final isLoading = isLoadingData.combineLatest<bool, bool>(
    isLoadingUser,
    (dataLoading, userLoading) => dataLoading || userLoading,
  );

  isLoading.listen((loading, _) => print('Loading: $loading'));

  // Prints initial: Loading: false

  isLoadingData.value = true;
  // Prints: Loading: true

  isLoadingUser.value = true;
  // Prints: Loading: true (both loading)

  isLoadingData.value = false;
  // Prints: Loading: true (user still loading)

  isLoadingUser.value = false;
  // Prints: Loading: false (both done)
}

Cómo Funciona

combineLatest() crea un nuevo ValueListenable que:

  1. Mantiene el último valor de ambas fuentes
  2. Llama a la función combinadora cuando cualquiera de las fuentes cambia
  3. Notifica a los listeners con el resultado combinado

Parámetros de Tipo

combineLatest<TIn2, TOut>() recibe dos parámetros de tipo:

  • TIn2 - Tipo del segundo ValueListenable
  • TOut - Tipo del resultado combinado
dart
final ageNotifier = ValueNotifier<int>(25);
final nameNotifier = ValueNotifier<String>('John');

// Combinar int y String en un tipo personalizado
final user = ageNotifier.combineLatest<String, User>(
  nameNotifier,
  (int age, String name) => User(age: age, name: name),
);

Casos de Uso Comunes

Validación de Formularios
dart
final email = ValueNotifier<String>('');
final password = ValueNotifier<String>('');

final isValid = email.combineLatest<String, bool>(
  password,
  (e, p) => e.contains('@') && p.length >= 8,
);

ValueListenableBuilder<bool>(
  valueListenable: isValid,
  builder: (context, valid, _) => ElevatedButton(
    onPressed: valid ? _submit : null,
    child: Text('Submit'),
  ),
);
Valores Computados
dart
final quantity = ValueNotifier<int>(1);
final price = ValueNotifier<double>(9.99);

final total = quantity.combineLatest<double, double>(
  price,
  (qty, p) => qty * p,
);

print(total.value); // 9.99

quantity.value = 3;
print(total.value); // 29.97
UI Condicional
dart
final isDarkMode = ValueNotifier<bool>(false);
final fontSize = ValueNotifier<double>(14.0);

final textStyle = isDarkMode.combineLatest<double, TextStyle>(
  fontSize,
  (dark, size) => TextStyle(
    color: dark ? Colors.white : Colors.black,
    fontSize: size,
  ),
);
Estado de Múltiples Fuentes
dart
final isLoading = ValueNotifier<bool>(false);
final hasError = ValueNotifier<bool>(false);

final uiState = isLoading.combineLatest<bool, UIState>(
  hasError,
  (loading, error) {
    if (loading) return UIState.loading;
    if (error) return UIState.error;
    return UIState.ready;
  },
);

Combinando Más de Dos Fuentes

Para combinar 3-6 ValueListenables, usa combineLatest3 hasta combineLatest6:

dart
final source1 = ValueNotifier<int>(1);
final source2 = ValueNotifier<int>(2);
final source3 = ValueNotifier<int>(3);

final sum = source1.combineLatest3<int, int, int>(
  source2,
  source3,
  (a, b, c) => a + b + c,
);

print(sum.value); // 6

Similarmente disponibles: combineLatest4, combineLatest5, combineLatest6

Cuándo Usar combineLatest()

Usa combineLatest() cuando:

  • ✅ Necesites valores de 2-6 ValueListenables
  • ✅ Quieras actualizar cuando cualquier fuente cambie
  • ✅ Necesites combinar valores en un nuevo tipo
  • ✅ Estés implementando estado derivado o propiedades computadas

mergeWith()

Fusiona cambios de valor de múltiples ValueListenables del mismo tipo. Se actualiza cuando cualquier fuente cambia, emitiendo el valor de esa fuente.

Uso Básico

dart
void main() {
  final listenable1 = ValueNotifier<int>(0);
  final listenable2 = ValueNotifier<int>(0);
  final listenable3 = ValueNotifier<int>(0);

  // Merge multiple ValueListenables - updates when ANY changes
  listenable1.mergeWith([listenable2, listenable3]).listen((x, _) => print(x));

  listenable2.value = 42;
  // Prints: 42

  listenable1.value = 43;
  // Prints: 43

  listenable3.value = 44;
  // Prints: 44

  listenable2.value = 45;
  // Prints: 45

  listenable1.value = 46;
  // Prints: 46
}

Cómo Funciona

mergeWith() crea un nuevo ValueListenable que:

  1. Se suscribe a la fuente principal y todas las fuentes en la lista
  2. Cuando cualquier fuente cambia, emite el valor actual de esa fuente
  3. Todas las fuentes deben ser del mismo tipo
dart
final source1 = ValueNotifier<int>(1);
final source2 = ValueNotifier<int>(2);
final source3 = ValueNotifier<int>(3);

final merged = source1.mergeWith([source2, source3]);

print(merged.value); // 1 (valor inicial de source1)

source2.value = 20;
print(merged.value); // 20 (source2 cambió)

source3.value = 30;
print(merged.value); // 30 (source3 cambió)

source1.value = 10;
print(merged.value); // 10 (source1 cambió)

Casos de Uso Comunes

Múltiples Fuentes de Eventos
dart
final userInput = ValueNotifier<String>('');
final apiResult = ValueNotifier<String>('');
final cacheData = ValueNotifier<String>('');

// Reaccionar a actualizaciones de cualquier fuente
final dataStream = userInput.mergeWith([apiResult, cacheData]);

dataStream.listen((data, _) => updateUI(data));
Múltiples Disparadores
dart
final saveButton = ValueNotifier<DateTime?>(null);
final autoSave = ValueNotifier<DateTime?>(null);
final shortcutKey = ValueNotifier<DateTime?>(null);

// Guardar disparado por cualquier acción
final saveTrigger = saveButton.mergeWith([autoSave, shortcutKey]);

saveTrigger.listen((timestamp, _) {
  if (timestamp != null) performSave();
});
Agregando Fuentes Similares
dart
final sensor1 = ValueNotifier<double>(0.0);
final sensor2 = ValueNotifier<double>(0.0);
final sensor3 = ValueNotifier<double>(0.0);

// Monitorear cualquier cambio de sensor
final anySensorChange = sensor1.mergeWith([sensor2, sensor3]);

anySensorChange.listen((value, _) => checkThreshold(value));

combineLatest() vs mergeWith()

CaracterísticacombineLatest()mergeWith()
Número de fuentes2-61 + N (array)
Tipos de fuentesPueden ser diferentesDeben ser del mismo tipo
Tipo de salidaPersonalizado (vía combinador)Mismo tipo que la fuente
Usar paraCombinar valores diferentesFusionar eventos similares
Valor de salidaResultado de la función combinadoraValor de la fuente que cambió

Ejemplo: Dos estados de carga

dart
final isLoadingData = ValueNotifier<bool>(false);
final isLoadingUser = ValueNotifier<bool>(false);

// combineLatest - combina ambos valores con lógica (operación OR)
final isLoading = isLoadingData.combineLatest<bool, bool>(
  isLoadingUser,
  (dataLoading, userLoading) => dataLoading || userLoading,
);

isLoadingData.value = true;
print(isLoading.value); // true (datos cargando)

isLoadingUser.value = true;
print(isLoading.value); // true (ambos cargando)

isLoadingData.value = false;
print(isLoading.value); // true (usuario aún cargando)

// mergeWith - solo toma el que cambió
final anyLoading = isLoadingData.mergeWith([isLoadingUser]);

isLoadingData.value = true;
print(anyLoading.value); // true (de isLoadingData)

isLoadingUser.value = false;
print(anyLoading.value); // false (de isLoadingUser - ¡no es lo que quieres!)

isLoadingData.value = false;
print(anyLoading.value); // false (de isLoadingData)

Diferencia clave: combineLatest() aplica lógica a ambos valores, mientras que mergeWith() solo emite la fuente que cambió - ¡haciéndolo incorrecto para este caso de uso!

Cuándo Usar mergeWith()

Usa mergeWith() cuando:

  • ✅ Tengas múltiples fuentes del mismo tipo
  • ✅ Quieras reaccionar a cambios de cualquier fuente
  • ✅ No necesites combinar valores, solo monitorear cualquier cambio
  • ✅ Estés agregando streams de eventos similares

Encadenando con Otros Operators

Los operators de combinación funcionan bien con otros operators:

dart
final firstName = ValueNotifier<String>('');
final lastName = ValueNotifier<String>('');

firstName
    .combineLatest<String, String>(
      lastName,
      (first, last) => '$first $last',
    )
    .where((name) => name.trim().isNotEmpty)
    .map((name) => name.toUpperCase())
    .listen((name, _) => print(name));

Ejemplo del Mundo Real

Total de carrito de compras con impuestos:

dart
final subtotal = ValueNotifier<double>(0.0);
final taxRate = ValueNotifier<double>(0.1);

final total = subtotal.combineLatest<double, double>(
  taxRate,
  (sub, rate) => sub * (1 + rate),
);

// Usar en UI
ValueListenableBuilder<double>(
  valueListenable: total,
  builder: (context, value, _) => Text('Total: \$${value.toStringAsFixed(2)}'),
);

Próximos Pasos

Publicado bajo la Licencia MIT.