Flutter: ¿Cómo funciona el enrutamiento?

El enrutamiento es la forma en que se maneja la navegación de una página a otra en una aplicación, y a su vez, es quien define el flujo de trabajo en la misma. Flutter proporciona un widget de mantenimiento de estado para el enrutamiento denominado Navigator, el cual cuenta con un grupo de funcionalidades que se usan en dependencia de la forma en que quieras ir de una pantalla a otra.

En este artículo vamos a explicar algunas de las formas de enrutamiento en Flutter según las acciones comunes que un usuario realiza en una aplicación como son:

  • Ir a una pantalla/página y volver atrás.
  • Ir a una pantalla/página remplazando la anterior.
  • Enviar datos a una segunda pantalla/página.
  • Obtener datos en la pantalla/página luego de volver atrás desde la segunda pantalla/página.

Para la realización del mismo, no vamos a mostrar todo el código, sino las partes relevantes del mismo, siempre puedes chequear todo el proyecto en el enlace que github que dejó al final.

Rutas nombradas vs MaterialPageRoute

Lo primero que debemos conocer es que en Flutter puedes navegar utilizando rutas nombradas (a mi entender mucho más cómodo) y a través de la clase MaterialPageRoute, veámoslo con dos ejemplos de como sería:

Para la rutas nombradas debes crear en el archivo starter main.dart o donde inicies con el widget MaterialApp el mapa de cada una de las rutas de esta forma (ver la propiedad routes):

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Enrutamiento',
      theme: ThemeData(
        primarySwatch: Colors.red,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Home(),
      routes: {
        "/home": (context) => Home(),
        "/navegar_y_volver": (context) => NavegarYVolver1(),
        "/enviar_datos": (context) => EnviarDatos1(),
        "/obtener_datos": (context) => ObtenerDatos1()
      },
    );
  }
}

Nota: La llave puede ser cualquier cadena, pude haber utilizado “HOME”, “NAVEGAR_Y_VOLVER” en vez de “/home”, “/navegar_y_volver”

Ahora veamos como lo utilizamos (este código simplemente muestra un botón que cuando se presiona en él remplaza la pantalla principal por el widget Home() asociado a la ruta “/home”):

RaisedButton(
    color: Colors.red,
    elevation: 5,
    child: Text('Home', style: TextStyle(color: Colors.white)),
    onPressed: () {
        Navigator.pushReplacementNamed(context, '/home');
    },
)

Si no vamos a utilizar rutas nombradas no es necesario escribir el mapa de rutas, veamos el botón para este caso utilizando la clase MaterialPageRoute.

RaisedButton(
    color: Colors.red,
    elevation: 5,
    child: Text('Home', style: TextStyle(color: Colors.white)),
    onPressed: () {
        Navigator.pushReplacement(
             context,
             MaterialPageRoute(builder: (context) => Home()),
        );
    },
)

Ir a una pantalla/página y volver atrás.

Veamos un ejemplo de navegación entre dos páginas y como podemos implementarlo para volver atrás (Esto es importante porque de esta forma nos va a salir la flecha de virar atrás en la barra de superior y además nos va a funcionar el botón back de nuestro dispositivo):

Archivo navegar_y_volver1.dart:

class NavegarYVolver1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Pagina 1'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              color: Colors.red,
              elevation: 5,
              child: Text('IR', style: TextStyle(color: Colors.white)),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => NavegarYVolver2()),
                );
              },
            ),
            RaisedButton(
              color: Colors.red,
              elevation: 5,
              child: Text('Home', style: TextStyle(color: Colors.white)),
              onPressed: () {
                Navigator.pushReplacementNamed(context, '/home');
              },
            )
          ],
        ),
      )
    );
  }
}

Resaltar aquí que no estamos utilizando rutas nombradas, sino la función Navigator.push(…), si quisiéramos utilizar una ruta nombrada quedaría de esta forma el botón:

RaisedButton(
    color: Colors.red,
    elevation: 5,
    child: Text('IR', style: TextStyle(color: Colors.white)),
    onPressed: () {
        Navigator.pushNamed(
            context,
            "/navegar_y_volver2"
        );
    },
)

Nota: Se debe crear, para este caso, la ruta “/navegar_y_volver2” y asociarla al widget NavegarYVolver2().

Archivo navegar_y_volver2.dart:

import 'package:flutter/material.dart';
class NavegarYVolver2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Pagina 2'),
      ),
      body: Center(
        child: RaisedButton(
          color: Colors.red,
          elevation: 5,
          child: Text('VOLVER', style: TextStyle(color: Colors.white)),
          onPressed: () {
            Navigator.pop(context) ;
          },
        ),
      ),
    );
  }
}

Para volver atrás en la segunda página simplemente invocamos la función Navigator.pop(…).

Ir a una pantalla/página remplazando la anterior

Para ir a otra pantalla ó página simplemente debemos utilizar las funciones correctas de Navigator, veamos los dos ejemplos, con y sin rutas nombradas (aunque esto lo vimos anteriormente):

RaisedButton(
    color: Colors.red,
    elevation: 5,
    child: Text('Ir al home', style: TextStyle(color: Colors.white)),
    onPressed: () {
       Navigator.pushReplacementNamed(context, '/home');
    },
)
RaisedButton(
    color: Colors.red,
    elevation: 5,
    child: Text('Ir al home', style: TextStyle(color: Colors.white)),
    onPressed: () {
       Navigator.pushReplacement(
             context,
             MaterialPageRoute(builder: (context) => Home()),
        );
    },
)

Enviar datos a una segunda pantalla/página.

En muchos casos que vamos de una página a otra queremos enviar alguna información, por ejemplo, supongamos que tenemos en una pantalla una lista de productos y queremos mostrar todos los detalles del producto, ya que en la lista solo nos cabe el nombre, la imagen y alguna otra información. Para esto necesitamos pasar los datos del producto o el identificador del mismo para obtener los detalles. Veamos como pasar datos:

Archivo enviar_datos1.dart

import 'package:flutter/material.dart';
import 'package:routes/pages/enviar_datos2.dart';
import 'package:routes/pages/navegar_y_volver2.dart';
class EnviarDatos1 extends StatelessWidget {
  String data = "Flutter es genial";
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Envio de datos'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(data, style: TextStyle(color: Colors.blue, fontSize: 24, fontWeight: FontWeight.bold)),
              RaisedButton(
                color: Colors.red,
                elevation: 5,
                child: Text('Enviar este texto', style: TextStyle(color: Colors.white)),
                onPressed: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(builder: (context) => EnviarDatos2(data: data,)),
                  );
                },
              ),
              RaisedButton(
                color: Colors.red,
                elevation: 5,
                child: Text('Home', style: TextStyle(color: Colors.white)),
                onPressed: () {
                  Navigator.pushReplacementNamed(context, '/home');
                },
              )
            ],
          ),
        )
    );
  }
}

Lo más relevante de este código está en la línea 27 como se pasan los datos.

Archivo enviar_datos2.dart

import 'package:flutter/material.dart';
import 'package:routes/pages/navegar_y_volver2.dart';
class EnviarDatos2 extends StatelessWidget {
  final String data;
  // En el constructor, se requiere la variable data (o puede ser un objeto)
  EnviarDatos2({Key key, @required this.data}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Envio de datos'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(data, style: TextStyle(color: Colors.green, fontSize: 28, fontWeight: FontWeight.bold)),
              RaisedButton(
                color: Colors.red,
                elevation: 5,
                child: Text('VOLVER', style: TextStyle(color: Colors.white)),
                onPressed: () {
                  Navigator.pop(context) ;
                },
              )
            ],
          ),
        )
    );
  }
}

Aquí pueden ver en la línea 9 como se pasan los datos por parámetro y se pueden utilizar en cualquier parte del widget.

Obtener datos desde una segunda página

En muchas ocasiones necesitamos obtener datos desde una segunda página para procesarlos en la primera, por ejemplo, estamos llenando un formulario y unos de los campos es un seleccionador de usuarios. Para seleccionarlo, necesitamos mostrar la lista de usuarios con sus detalles. Para este caso tenemos que ir a una nueva pantalla donde está la lista de usuarios y seleccionarlo y volver atrás con el usuario marcado.

Archivo obtener_datos1.dart:

import 'package:flutter/material.dart';
import 'package:routes/pages/obtener_datos2.dart';
class ObtenerDatos1 extends StatefulWidget {
  ObtenerDatos1({Key key}) : super(key: key);
  @override
  _ObtenerDatos1State createState() => _ObtenerDatos1State();
}
class _ObtenerDatos1State extends State<ObtenerDatos1>  {
  String data = "Este texto cambiara al volver" ;
  _navigateToObtenerDatos2(BuildContext context) async {
     final r = await Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => ObtenerDatos2()),
    );
     setState(() {
       data = r ;
     });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Envio de datos'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(data, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.deepPurple)),
              RaisedButton(
                color: Colors.red,
                elevation: 5,
                child: Text('Obtener dato', style: TextStyle(color: Colors.white)),
                onPressed: () {
                  _navigateToObtenerDatos2(context) ;
                },
              ),
              RaisedButton(
                color: Colors.red,
                elevation: 5,
                child: Text('Home', style: TextStyle(color: Colors.white)),
                onPressed: () {
                  Navigator.pushReplacementNamed(context, '/home');
                },
              )
            ],
          ),
        )
    );
  }
}

En este caso estamos utilizando un widget de mantenimiento de estado, ya que el valor de la variable data debe cambiar al volver de la segunda pantalla. Resaltar en este código la funcionalidad _navigateToObtenerDatos2 (como explicamos en artículos anteriores cuando comienza con _ es porque es privada) que es un método asíncrono que se queda esperando una respuesta de la segunda pantalla para adicionarla en la variable r y luego asignarla en la variable data.

Archivo obtener_datos2.dart:

import 'package:flutter/material.dart';
class ObtenerDatos2 extends StatelessWidget {
  ObtenerDatos2({Key key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Obtener de datos'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              RaisedButton(
                color: Colors.red,
                elevation: 5,
                child: Text('Enviar "BaseBall"', style: TextStyle(color: Colors.white)),
                onPressed: () {
                  Navigator.pop(context, "BaseBall") ;
                },
              ),
              RaisedButton(
                color: Colors.red,
                elevation: 5,
                child: Text('Enviar "Football"', style: TextStyle(color: Colors.white)),
                onPressed: () {
                  Navigator.pop(context, "Football") ;
                },
              )
            ],
          ),
        )
    );
  }
}

Como pueden apreciar en los dos botones, se llama a la clase Navigator.pop(…), pero en este caso pasando el contexto y el dato que se desea enviar a la primera pantalla.

Hasta este punto creo que hemos visto todas las acciones que un usuario realiza en una aplicación en cuanto a navegación, como siempre digo, hay cuestiones que se nos pueden escapar y que seguro estudiando y practicando las podrás ver en el camino.

Puedes descargar el código en este enlace: github