flutter-navegacion-paso-datos

Pasar datos entre pantallas en Flutter

  • 4 min

El paso de datos es enviar información de una pantalla a otra durante la navegación.

Moverse de una pantalla a otra está muy bien, pero una App que no comparte información es una App sorda.

Necesitamos pasar datos: decirle a la pantalla de “Detalle” qué producto ha pulsado el usuario. Y también necesitamos recibir respuestas: que la pantalla de “Selección de Fecha” le diga a la pantalla anterior qué día escogió el usuario.

Hoy vamos a ver este flujo bidireccional: enviar argumentos y recibir resultados.

Enviar datos hacia adelante (A -> B)

La forma más sencilla y robusta de pasar datos a una nueva pantalla es utilizando el Constructor del Widget destino.

Si tratamos a las pantallas como lo que son (Clases de Dart), pasar datos es tan simple como instanciar esa clase con parámetros.

Preparar la pantalla de destino

Primero, definimos qué datos necesita la pantalla para funcionar. Usamos propiedades final y las pedimos en el constructor.

class PantallaDetalle extends StatelessWidget {
  // 1. Definimos el dato que esperamos recibir
  final String nombreProducto;
  final double precio;

  // 2. Lo exigimos en el constructor
  const PantallaDetalle({
    super.key, 
    required this.nombreProducto,
    required this.precio
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(nombreProducto)),
      body: Center(child: Text("Cuesta \$$precio")),
    );
  }
}
Copied!

Enviar el dato desde el origen

Al hacer el Navigator.push, simplemente pasamos los datos al crear el widget.

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => PantallaDetalle(
      nombreProducto: "Curso de Flutter", // Pasamos el dato
      precio: 99.99,
    ),
  ),
);
Copied!

¡Así de simple! Sin trucos extraños ni variables globales.

Recibir datos de vuelta (B -> A)

Aquí la cosa se pone interesante. ¿Cómo sabe la pantalla A que la pantalla B ha terminado y qué ha decidido el usuario?

La clave está en recordar que Navigator.push es una función Asíncrona que devuelve un Future.

Esperar el resultado con await

En la pantalla de origen, debemos usar await para pausar la ejecución de la función hasta que el usuario vuelva.

Future<void> _seleccionarCiudad() async {
  // Esperamos a que la pantalla se cierre y guardamos lo que devuelva
  final resultado = await Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => PantallaSeleccionCiudad()),
  );

  // Cuando la ejecución llega aquí, la pantalla B ya se ha cerrado
  if (resultado != null) {
    print("El usuario eligió: $resultado");
  }
}
Copied!

Devolver el dato con pop

En la pantalla de destino (Selección), cuando queramos cerrar y enviar el dato, lo pasamos como segundo argumento del método pop.

// Dentro de PantallaSeleccionCiudad
ListTile(
  title: Text("Madrid"),
  onTap: () {
    // Cerramos y enviamos "Madrid" de vuelta
    Navigator.pop(context, "Madrid");
  },
);
Copied!

El peligro del botón “Atrás”

Hay un detalle crítico: El usuario podría no seleccionar nada y simplemente pulsar la flecha de “Atrás” en el AppBar o el botón físico de Android.

En ese caso, la ruta se cierra sin resultado y el Future devuelve null.

Por eso, siempre debemos comprobar si el resultado es nulo antes de usarlo:

if (resultado != null) {
  // Hacemos algo con el dato
} else {
  // El usuario canceló la operación
}
Copied!

Ejemplo completo: selector de emojis

Vamos a hacer un ejemplo rápido. Una pantalla principal con un botón “Elige tu estado de ánimo”, que abre una lista de emojis. Al pulsar uno, volvemos y actualizamos el texto.

// --- PANTALLA 1: HOME ---
class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  String _emojiSeleccionado = "❓";

  Future<void> _navegarYEsperar() async {
    // 1. Vamos y ESPERAMOS (await)
    final resultado = await Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => SeleccionScreen()),
    );

    // 2. Verificamos si trajo algo (si no volvió con "Atrás")
    if (!mounted) return;

    if (resultado != null) {
      setState(() {
        _emojiSeleccionado = resultado;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Paso de Datos")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(_emojiSeleccionado, style: TextStyle(fontSize: 50)),
            ElevatedButton(
              onPressed: _navegarYEsperar,
              child: Text("Seleccionar Estado"),
            )
          ],
        ),
      ),
    );
  }
}

// --- PANTALLA 2: SELECCIÓN ---
class SeleccionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Elige uno")),
      body: ListView(
        children: [
          ListTile(
            title: Text("Feliz 😀"),
            onTap: () => Navigator.pop(context, "😀"), // Devuelve emoji
          ),
          ListTile(
            title: Text("Triste 😢"),
            onTap: () => Navigator.pop(context, "😢"),
          ),
          ListTile(
            title: Text("Programando 👨‍💻"),
            onTap: () => Navigator.pop(context, "👨‍💻"),
          ),
        ],
      ),
    );
  }
}
Copied!