Navegación y enrutamiento en Flutter
La navegación es uno de los temas que más dolores de cabeza causa cuando comienzas con Flutter… y, curiosamente, también uno de los que más fácil vuelve tu vida cuando logras dominarlo. En mis primeros proyectos me di cuenta de que casi todo se apoya en lo mismo: gestionar una pila de rutas como si fuera una pila real (push, pop… y algo más de magia).
En esta guía te llevo desde cero hasta lo avanzado: Navigator básico, Navigator 2.0, go_router, rutas anidadas, casos prácticos y buenas prácticas reales que me han salvado proyectos más de una vez.
Podemos comparar la navegación en Flutter con el funcionamiento de la estructura de datos de la pila.
Seguimos del post anterior en la cual, aprendimos a usar el FutureBuilder, async, await en Flutter.
Qué es la navegación en Flutter y cómo funciona la pila de rutas
Cómo Flutter gestiona pantallas como rutas
En Flutter, cada pantalla es una ruta que se apila sobre otra. Cuando navegas, empujas una ruta al stack; cuando vuelves atrás, la retiras.
Cuando lo entendí así —como una simple pila— todo cobró sentido: “Si empujo 5 pantallas, necesitaré 5 pops para volver”. Y de verdad, en mis primeras apps (listas de productos, pantallas de detalles, etc.) me sirvió muchísimo tener esta analogía mental para evitar errores tontos.
Diferencias entre navegación móvil y Flutter Web
En apps móviles:
- prima la animación
- las transiciones importan
- el historial es interno de la app
En Flutter Web:
- las rutas se vuelven URLs reales
- el botón “Atrás” del navegador importa
- necesitas que tu navegación sea declarativa
Navigator en Flutter: conceptos esenciales y primeros pasos
Tenga en cuenta que las pantallas en Flutter se llaman rutas, pero para simplificar, usaré la palabra página para referirse a las pantallas. Flutter usa la clase de navegación para la navegación. Hay cinco métodos en la clase de navegación que discutiremos en este artículo:
- Navigator.push()
- Navigator.pop()
- Navigator.pushReplacement()
- Navigator.pushAndRemoveUntil()
- Navigator.popUntil()
Cuando estamos creando una aplicación como una aplicación de compras en línea, queremos mostrar los detalles de un artículo en una nueva página cuando el usuario selecciona un artículo de una lista y también queremos volver a la página principal cuando el usuario hace clic atrás. Esto se puede implementar usando los métodos:
- Navigator.push()
- Navigator.pop()
La clase Navigator proporciona todas las capacidades de navegación en una aplicación Flutter.
Navigator proporciona métodos para mutar la pila agregando y removiendo páginas de la misma mediante los métodos señalados anteriormente. El método Navigator.push es para navegar a una página más nueva y Navigator.pop es para regresar desde la página actual.
Estos son ejemplos básicos de pop y push: el método push toma BuildContext como primer argumento y el segundo argumento es PageBuilder.
Navigator.push y Navigator.pop explicados de forma práctica
- Navigator.push() agrega una nueva pantalla.
Navigator.pop() te devuelve.
Cuando hice mi primera app con pantallas de detalles, lo viví así: "Presiono un item → push; vuelvo → pop”. Simple, directo, efectivo.
Navigator.push(
context,
MaterialPageRoute(builder: (_) => DetalleScreen()),
);pushReplacement, pushAndRemoveUntil y popUntil en flujos reales
Los métodos más importantesÑ
- pushReplacement(): ideal para pantallas de login → home.
En uno de mis proyectos usé pushReplacement para evitar que el usuario volviera a la pantalla de inicio de sesión… y fue mano de santo. - pushAndRemoveUntil(): perfecto para cerrar sesión o resetear flujo.
En una app con checkout, cuando el pago se completaba, eliminaba todo el stack antes de llevar al usuario al resumen. - popUntil(): útil cuando quieres regresar a una pantalla específica.
Yo lo usé para saltar de la pantalla de pago directamente a la pantalla de confirmación sin que el usuario viera las intermedias hacia atrás.
Pasar datos entre pantallas: argumentos, ModalRoute y resultados
Puedes pasar argumentos así:
Navigator.pushNamed(
context,
'/detalle',
arguments: {'id': 25, 'nombre': 'Pepe'},
);Y los recuperas con:
final args = ModalRoute.of(context)!.settings.arguments;Rutas con nombre: cuándo convienen y cómo organizarlas
Las rutas con nombre le permiten cambiar la ruta mediante el uso de cadenas en lugar de proporcionar clases de componentes, lo que a su vez le permite reutilizar el código.
- navegación repetitiva
- mayor mantenibilidad
- rutas estructuradas
- filtros, parámetros, detalles, etc.
Definición de rutas
La ruta es un mapa con claves de cadena y valores como constructores que se pasan a la propiedad de rutas en MaterialApp:
void main() {
runApp(MaterialApp(
title: 'My App',
home: Main(),
// Routes defined here
routes: {
"page":(context)=>PageRoute()
},
));
}Uso de rutas con nombre
En lugar de push, pushNamed se usa para cambiar a una nueva ruta. De manera similar, *pushReplacementNamed* se usa en lugar de pushReplacement. El método pop es el mismo para todas las rutas.
class Main extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Route'),
),
body: Center(
child:RaisedButton(
child: Text('Open route'),
onPressed: () {
Navigator.pushReplacementNamed(context, "page");
},
),
),
);
}Navigator 2.0: navegación declarativa y manejo del historial
Qué resuelve Navigator 2.0 y por qué es clave en Flutter Web
Navigator 2.0 nació para resolver:
- deep linking complejo
- sincronizar UI ↔ URL
- flujos no lineales
- mayor control del historial
En una app web que construí, usar RouterDelegate me permitió que la URL siempre reflejara el estado real de la navegación.
RouterDelegate y RouteInformationParser en palabras simples
- RouteInformationParser: interpreta la URL.
- RouterDelegate: decide qué pantallas mostrar.
Caso práctico: navegación basada en URL y deep linking
Deep linking deja que alguien abra tu app directamente en:
- /producto/123
- /perfil/editar
- /tarea/7
Navigator 2.0 lo soporta de forma nativa.
go_router en Flutter: navegación moderna y declarativa
Por qué go_router simplifica la navegación en apps grandes
Aquí viene la parte que cambió mi vida de desarrollador Flutter: go_router.
En mis proyectos grandes, organizados por features, noté que Navigator 1.0 se volvía inmanejable. go_router me dio:
- rutas declarativas
- URLs limpias
- parámetros más claros
- rutas anidadas fáciles
- redirecciones poderosas
Definición declarativa de rutas y parámetros dinámicos
GoRoute(
path: 'details/:id',
builder: (_, state) => DetalleScreen(id: state.params['id']!),
);Uso de context.go, goNamed y extracción de parámetros
- context.go('/detalles')
- context.goNamed('detalle', params: {'id': '8'})
Redirecciones, protección de rutas y control del estado
Puedes bloquear rutas no autorizadas:
redirect: (_, state) {
if (!logueado) return '/login';
return null;
}Transiciones personalizadas con go_router
Puedes animar cada ruta con CustomTransitionPage y transitionsBuilder.
Rutas anidadas y organización del proyecto por features
Cómo estructurar módulos y rutas hijas con go_router
Aquí entra tu ejemplo perfecto: lista de tareas ⇒ detalles ⇒ agregar tarea.
go_router permite declarar rutas hijas:
GoRoute(
path: '/',
routes: [
GoRoute(
path: 'details/:taskId',
builder: ...
),
],
);Caso práctico: lista de tareas con detalles y pantalla de creación
Cuando implementé una app de tareas, descubrí algo clave:
- lista de tareas es la ruta padre
- detalles de tarea es la ruta hija
- añadir tarea es otra hija
- todo queda legible y modular
Beneficios de la navegación anidada en apps escalables
- Mantenimiento más fácil
- URLs predecibles
- Separación por features
- Control total del flujo
Comparativa completa: Navigator vs Navigator 2.0 vs go_router
- Qué elegir según el tamaño y complejidad de la aplicación
- Escenario Mejor opción
- App pequeña Navigator 1.0
- App mediana con estados simples Rutas con nombre
- App grande/modular go_router
- App Flutter Web Navigator 2.0 o go_router
- Deep linking avanzado go_router o Nav 2.0
Tabla comparativa de casos de uso reales
- Caso real Solución recomendada
- Evitar regresar al login pushReplacement / redirect
- Navegación por URL go_router / Nav 2.0
- Flujos tipo checkout pushAndRemoveUntil
- Rutas hijas go_router
- SPA estilo web go_router
Buenas prácticas para evitar errores comunes
- No mezclar Navigator 1.0 y go_router sin necesidad
- Mantener las rutas fuera del UI
- Usar nombres de rutas, no paths hardcodeados
- Evitar estados acoplados a rutas
Buenas prácticas y patrones para una navegación robusta
Organización de rutas por módulos o features
En apps medianas o grandes siempre uso carpetas por feature:
/features/tareas
/presentation
/routes
/data
Manejo de estado en flujos de navegación
Según el tamaño:
- pequeño → setState
- mediano → provider
- grande → riverpod o bloc
Limpieza de código y arquitectura recomendada
- Separar navegación de la UI
- Usar modelos para parámetros complejos
- Evitar lógica en los builders de las rutas
NO uses push(), usa pushReplacement() en el Navigator con las rutas
Índice de contenido
- Qué es la navegación en Flutter y cómo funciona la pila de rutas
- Cómo Flutter gestiona pantallas como rutas
- Diferencias entre navegación móvil y Flutter Web
- Navigator en Flutter: conceptos esenciales y primeros pasos
- Navigator.push y Navigator.pop explicados de forma práctica
- pushReplacement, pushAndRemoveUntil y popUntil en flujos reales
- Rutas con nombre: cuándo convienen y cómo organizarlas
- Definición de rutas
- Uso de rutas con nombre
- Navigator 2.0: navegación declarativa y manejo del historial
- RouterDelegate y RouteInformationParser en palabras simples
- Caso práctico: navegación basada en URL y deep linking
- Navigator 2.0 lo soporta de forma nativa.
- Definición declarativa de rutas y parámetros dinámicos
- Uso de context.go, goNamed y extracción de parámetros
- Redirecciones, protección de rutas y control del estado
- Transiciones personalizadas con go_router
- Rutas anidadas y organización del proyecto por features
- Caso práctico: lista de tareas con detalles y pantalla de creación
- Tabla comparativa de casos de uso reales
- Buenas prácticas para evitar errores comunes
- Buenas prácticas y patrones para una navegación robusta
- Organización de rutas por módulos o features
- Manejo de estado en flujos de navegación
- Limpieza de código y arquitectura recomendada
- NO uses push(), usa pushReplacement() en el Navigator con las rutas
- Uso de Navigator.push()
- Solución con Navigator.pushReplacement()
- Preguntas frecuentes sobre navegación en Flutter
- Conclusión: cómo elegir la mejor estrategia de navegación para tu app Flutter
Quería contar una anécdota que me pasó al momento de crear la aplicación DesarrolloLibre, que es la que estás viendo en pantalla.
El problema surgió con el componente DrawerComponent, que es el que me permite navegar entre las diversas páginas de la aplicación. Específicamente, el inconveniente se presentaba en el listado de todos los cursos: cuando entraba al detalle de un curso y luego quería ir al detalle de otro, tenía que dar clic en el DrawerComponent y después en "Todos los cursos".
Uso de Navigator.push()
Antes, estaba utilizando:
Navigator.push();El problema con Navigator.push() es que encola cada una de las vistas anteriores. Por lo tanto, si navegabas entre varios cursos, al presionar "Atrás" regresabas al curso anterior. Por ejemplo, si necesitabas revisar 10 cursos, se habían encolado 10 vistas anteriores.
Esto generaba un problema adicional: cuando se ejecutaba el dispose() en el widget de los cursos, el estado de la página actual se mezclaba con el de las páginas anteriores. Esto provocaba que la aplicación crasheara al salirse del rango al intentar consumir alguno de los listados de clases.
Solución con Navigator.pushReplacement()
Lo importante es identificar qué necesita realmente tu aplicación para funcionar correctamente.
En mi caso, no tenía sentido mantener encoladas todas las vistas anteriores, ya que solo consumían recursos innecesarios.
La mejor solución fue utilizar:
Navigator.pushReplacement();Con esto, cada vez que navegamos a un curso nuevo, se reemplaza la página anterior, evitando la acumulación de vistas y los problemas de estado que mencioné.
Preguntas frecuentes sobre navegación en Flutter
- Diferencias entre go y goNamed
- go → usa el path directo
- goNamed → usa el nombre (más seguro y escalable)
- Cómo proteger rutas con redirecciones
- Con redirect en go_router o mediante guardas en RouterDelegate.
- Cómo pasar datos adicionales a una ruta
- Con extra:
context.goNamed('detalle', extra: {'modo': 'edicion'});
- Cómo controlar el historial en Flutter Web
- Usa navegación declarativa y evita manipular el stack manualmente.
Conclusión: cómo elegir la mejor estrategia de navegación para tu app Flutter
La navegación es más que ir de una pantalla a otra: es la columna vertebral del flujo de tu app. Con Navigator 1.0 puedes avanzar rápido; con Navigator 2.0 ganas control; y con go_router consigues una navegación moderna, declarativa y escalable.
En mi experiencia construyendo apps con listas, detalles, login, flujos de pago y versiones web, lo que más me ha funcionado es:
- Navigator 1.0 para prototipos y apps simples
- go_router para cualquier cosa medianamente seria
- Navigator 2.0 si necesito control quirúrgico del historial o deep linking
Dominar las tres herramientas te hará más rápido, más flexible y te permitirá crear apps robustas sin enredarte en la navegación.
Siguiente paso, descubre el Widget Scaffold de Flutter.
Acepto recibir anuncios de interes sobre este Blog.
Veamos como trabajar con la navegacion en Flutter con las funciones de: Navigator.push() Navigator.pop() Navigator.pushReplacement() Navigator.pushAndRemoveUntil() Navigator.popUntil() También, veremos como utilizar go router para la navegación moderna en Flutter.