Un responsable de producto nos escribe: «¿Podemos tener eso del iPhone que muestra el pedido en la isla dinámica?». La respuesta corta es sí. La respuesta útil es que eso no se construye en Flutter, y que conviene saberlo antes de prometerle una fecha al negocio.

La UI de un widget de pantalla de inicio y la de una Live Activity son nativas, siempre. Flutter mueve los datos de un lado a otro; SwiftUI o Jetpack Compose las dibujan. Quien ignora esa frontera estima un «widget de Flutter» y acaba con una extensión de Xcode, un provider de Android y un backend de push que nadie había presupuestado.

Son tres superficies, no una

Se mezclan en las reuniones, pero son cosas distintas con reglas distintas.

Un widget de pantalla de inicio es ese recuadro que vive junto a los iconos. Muestra un estado que cambia poco: el tiempo, tu próximo evento, un saldo. En iOS es WidgetKit (desde iOS 14); en Android, los App Widgets de siempre o Glance, su versión moderna sobre Compose (Android 12+). No es tiempo real: el sistema decide cuándo refrescarlo y raciona esos refrescos para cuidar la batería.

Una Live Activity es iOS puro. Aparece en la pantalla de bloqueo y, en los iPhone 14 Pro y posteriores, en la isla dinámica. Sirve para un estado que evoluciona durante un rato acotado: un pedido que llega, un partido en directo, un viaje en curso. Necesita iOS 16.1.

Un Live Update es la respuesta de Android, disponible desde Android 16. Por debajo es una notificación de progreso (Notification.ProgressStyle) que el sistema promociona a un chip en la barra de estado. Google es explícito sobre para qué sirve: una actividad en curso, iniciada por la persona y sensible al tiempo. Y sobre para qué no: el seguimiento pasivo de un paquete que llega en tres días no es buen candidato.

Lo que Flutter puede hacer, y lo que no

Flutter no renderiza dentro de ninguna de estas superficies. No hay un motor de Flutter corriendo en la isla dinámica ni en el widget de la pantalla de inicio. La UI es una extensión nativa:

  • En iOS, un Widget Extension en SwiftUI, declarado con ActivityConfiguration para Live Activities o con WidgetKit para el widget estático.
  • En Android, RemoteViews o un layout de Glance.

Lo que sí hace Flutter es pasar los datos. El paquete de referencia es home_widget (v0.10.0, de Anton Borries): te da una interfaz común para guardar datos desde Dart, leerlos desde el widget nativo y pedir un refresco. En iOS comparte datos a través de un App Group; en ambas plataformas, el widget nativo lee ese almacenamiento compartido y se dibuja. También puede rasterizar un widget de Flutter a imagen y mostrarlo en el recuadro estático, un atajo cómodo cuando la marca manda sobre la interactividad. Para Live Activities el atajo no existe: la isla dinámica se escribe en SwiftUI y no hay forma de evitarlo.

Si tu equipo ya ha tocado canales nativos, esto es más de lo mismo. Si no, es justo el tipo de interoperabilidad que conviene mirar antes de comprometer fechas; lo contamos en nuestra guía de interop nativo con Flutter.

iOS: cómo es una Live Activity por dentro

El estado viaja en un tipo ActivityAttributes con un ContentState que es Codable y Hashable. La parte atemporal (un identificador de pedido) va en los atributos; lo que cambia (los minutos que faltan) va en el estado.

import ActivityKit
import WidgetKit
import SwiftUI

struct DeliveryAttributes: ActivityAttributes {
    public struct ContentState: Codable, Hashable {
        var etaMinutes: Int
    }
    var orderID: String
}

struct DeliveryLiveActivity: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: DeliveryAttributes.self) { context in
            Text("Llega en \(context.state.etaMinutes) min")   // pantalla de bloqueo
        } dynamicIsland: { context in
            DynamicIsland {
                DynamicIslandExpandedRegion(.center) {
                    Text("Pedido \(context.attributes.orderID)")
                }
            } compactLeading: {
                Text("🚗")
            } compactTrailing: {
                Text("\(context.state.etaMinutes)m")
            } minimal: {
                Text("🚗")
            }
        }
    }
}

Actualizarla tiene dos caminos. Con la app abierta, llamas a Activity.update(...). Con la app cerrada, que es lo normal en un reparto, necesitas push: APNs con apns-push-type: liveactivity (desde iOS 16.2) y, si quieres arrancar la actividad en remoto sin que el usuario abra la app, push-to-start (desde iOS 17.2). El payload va limitado a unos 4 KB, así que mandas el estado, no el mundo.

Los límites marcan el diseño. Una Live Activity está activa hasta 8 horas y puede quedarse en la pantalla de bloqueo unas 4 horas más, unas 12 en total antes de que el sistema la retire. Puedes pedir actualizaciones frecuentes con la clave NSSupportsLiveActivitiesFrequentUpdates, pero «frecuentes» no es «ilimitadas»: iOS mantiene un presupuesto y lo aplica. Si tu caso necesita refrescar cada dos segundos durante horas, la Live Activity no es la herramienta.

Android no es iOS con otro nombre

Aquí no hay equivalencia uno a uno, y tratarla como si la hubiera es el error caro. El widget de pantalla de inicio de Android es más viejo y más flexible que el de iOS. El Live Update de Android 16, en cambio, es más joven que la Live Activity y está pensado alrededor del progreso: una barra que avanza, un chip con setShortCriticalText en la barra de estado. Construyes dos veces, con dos modelos mentales, y el diseño que funciona en la isla dinámica rara vez se traslada tal cual al chip de Android.

Cuándo sí y cuándo no

Caso de usoiOSAndroid¿Merece la pena?
Presencia de marca, dato que cambia pocoWidget WidgetKitApp Widget / GlanceSí, es lo más barato y duradero
Pedido o reparto en cursoLive ActivityLive UpdateSí, si hay un estado efímero real
Partido, viaje o timer en directoLive ActivityLive UpdateSí, con push bien montado
Seguimiento pasivo de un envío a 3 días——No; Google lo desaconseja y iOS lo penaliza en batería
Dato que cambia cada segundo durante horas——No; chocas con el presupuesto de refresco

La regla que aplicamos: si no existe un momento con «algo en curso» que la persona quiera vigilar sin abrir la app, no fuerces la isla dinámica porque quede moderno. Un widget estático bien hecho rinde más y cuesta menos.

Qué presupuestar de verdad

Esto no es «un widget de Flutter». Es trabajo nativo a ambos lados más un puente de datos. Cuando estimamos uno de estos encargos, las partidas son la extensión en SwiftUI, la configuración de App Groups y firmas, el provider o el Glance en Android, y, si el estado se actualiza en remoto, el backend de push por APNs y FCM. Ese último trozo es el que más se subestima: una Live Activity sin push es una foto fija.

Nuestra recomendación práctica: empieza por el widget de pantalla de inicio si lo que quieres es presencia. Es más barato, vive más tiempo y no depende de un pipeline de push. Reserva las Live Activities y los Live Updates para flujos donde de verdad hay un estado que corre (un pedido, un viaje, un directo) y donde la persona gana algo real al no tener que abrir la app. Lo demás es isla dinámica por la isla dinámica.

¿Tienes un flujo así y quieres saber qué cuesta llevarlo a las dos tiendas? Hablamos de tu caso.