Desarrollo web y de escritorio receptivo con Flutter
Publicado: 2022-03-10Este tutorial no es una introducción a Flutter en sí. Hay muchos artículos, videos y varios libros disponibles en línea con introducciones simples que lo ayudarán a aprender los conceptos básicos de Flutter. En su lugar, cubriremos los siguientes dos objetivos:
- El estado actual del desarrollo no móvil de Flutter y cómo puede ejecutar el código de Flutter en el navegador, en una computadora de escritorio o portátil;
- Cómo crear aplicaciones receptivas usando Flutter, para que pueda ver su poder, especialmente como un marco web, en pantalla completa, terminando con una nota sobre el enrutamiento basado en URL.
¡Entremos en ello!
Qué es Flutter, por qué es importante, en qué ha evolucionado, hacia dónde se dirige
Flutter es el marco de desarrollo de aplicaciones más reciente de Google. Google prevé que sea integral: permitirá que el mismo código se ejecute en teléfonos inteligentes de todas las marcas, en tabletas y en computadoras de escritorio y portátiles como aplicaciones nativas o como páginas web.
Es un proyecto muy ambicioso, pero Google ha tenido un éxito increíble hasta ahora, particularmente en dos aspectos: en la creación de un marco verdaderamente independiente de la plataforma para aplicaciones nativas de Android e iOS que funciona muy bien y está completamente listo para su uso en producción, y en la creación de un frente impresionante -end framework web que puede compartir el 100% del código con una aplicación Flutter compatible.
En la siguiente sección, veremos qué hace que la aplicación sea compatible y cuál es el estado del desarrollo de Flutter no móvil a partir de ahora.
Desarrollo no móvil con Flutter
El desarrollo no móvil con Flutter se publicitó por primera vez de manera significativa en Google I/O 2019. Esta sección trata sobre cómo hacer que funcione y cuándo funciona.
Cómo habilitar el desarrollo web y de escritorio
Para habilitar el desarrollo web, primero debe estar en el canal beta de Flutter. Hay dos formas de llegar a ese punto:
- Instale Flutter directamente en el canal beta descargando la última versión beta adecuada del archivo SDK.
- Si ya tienes instalado Flutter, cambia al canal beta con
$ flutter channel beta
y luego realiza el cambio actualizando tu versión de Flutter (que en realidad es ungit pull
en la carpeta de instalación de Flutter) con$ flutter upgrade
.
Después de eso, puedes ejecutar esto:
$ flutter config --enable-web
El soporte de escritorio es mucho más experimental, especialmente debido a la falta de herramientas para Linux y Windows, lo que hace que el desarrollo de complementos sea especialmente un gran dolor, y debido al hecho de que las API utilizadas están destinadas para uso de prueba de concepto y no para producción. Esto es diferente al desarrollo web, que utiliza el compilador dart2js de eficacia probada para las compilaciones de lanzamiento, que ni siquiera son compatibles con las aplicaciones de escritorio nativas de Windows y Linux.
Nota : la compatibilidad con macOS es un poco mejor que la compatibilidad con Windows y Linux, pero aún no es tan buena como la compatibilidad con la web y no tan buena como la compatibilidad total con las plataformas móviles.
Para habilitar la compatibilidad con el desarrollo de escritorio, debe cambiar al canal de versión master
siguiendo los mismos pasos descritos anteriormente para el canal beta
. Luego, ejecute lo siguiente reemplazando <OS_NAME>
con linux
, windows
o macos
:
$ flutter config --enable-<OS_NAME>-desktop
En este punto, si tiene problemas con cualquiera de los siguientes pasos que describiré porque la herramienta Flutter no está haciendo lo que digo que debería hacer, algunos pasos comunes para la solución de problemas son estos:
- Ejecute
flutter doctor
para comprobar si hay problemas. Un efecto secundario de este comando de Flutter es que debe descargar cualquier herramienta que necesite y que no tenga. - Ejecute la
flutter upgrade
. - Apáguelo y vuelva a encenderlo. La antigua respuesta de soporte técnico de nivel 1 de reiniciar su computadora podría ser justo lo que necesita para poder disfrutar de todas las riquezas de Flutter.
Ejecución y creación de aplicaciones web de Flutter
El soporte web de Flutter no está nada mal, y esto se refleja en la facilidad de desarrollo para la web.
Ejecutando esto…
$ flutter devices
… debería mostrar de inmediato una entrada para algo como esto:
Web Server • web-server • web-javascript • Flutter Tools
Además, ejecutar el navegador Chrome debería hacer que Flutter también muestre una entrada para él. Ejecutar flutter run
en un proyecto Flutter compatible (más sobre esto más adelante) cuando el único "dispositivo conectado" que aparece es el servidor web hará que Flutter inicie un servidor web en localhost:<RANDOM_PORT>
, lo que le permitirá acceder a su Flutter aplicación web desde cualquier navegador.
Si instaló Chrome pero no aparece, debe configurar la variable de entorno CHROME_EXECUTABLE
en la ruta al archivo ejecutable de Chrome.
Ejecutar y crear aplicaciones de escritorio de Flutter
Después de habilitar la compatibilidad con escritorio de Flutter, puede ejecutar una aplicación de Flutter de forma nativa en su estación de trabajo de desarrollo con flutter run -d <OS_NAME>
, reemplazando <OS_NAME>
con el mismo valor que usó al habilitar la compatibilidad con escritorio. También puedes compilar archivos binarios en el directorio de build
con flutter build <OS_NAME>
.
Sin embargo, antes de que pueda hacer algo de eso, debe tener un directorio que contenga lo que Flutter necesita construir para su plataforma. Esto se creará automáticamente cuando crees un nuevo proyecto, pero deberás crearlo para un proyecto existente con flutter create .
. Además, las API de Linux y Windows son inestables, por lo que es posible que deba regenerarlas para esas plataformas si la aplicación deja de funcionar después de una actualización de Flutter.
¿Cuándo es compatible una aplicación?
¿Qué quise decir todo el tiempo cuando mencioné que una aplicación Flutter tiene que ser un "proyecto compatible" para que funcione en el escritorio o en la web? En pocas palabras, quiero decir que no debe usar ningún complemento que no tenga una implementación específica de plataforma para la plataforma en la que está tratando de construir.
Para dejar este punto absolutamente claro para todos y evitar malentendidos, tenga en cuenta que un complemento de Flutter es un paquete particular de Flutter que contiene un código específico de la plataforma que es necesario para que brinde sus funciones.
Por ejemplo, puede usar el paquete url_launcher
desarrollado por Google tanto como desee (y es posible que desee, dado que la web se basa en hipervínculos).
Un ejemplo de un paquete desarrollado por Google cuyo uso impediría el desarrollo web es path_provider
, que se usa para obtener la ruta de almacenamiento local para guardar archivos. Este es un ejemplo de un paquete que, por cierto, no es de ninguna utilidad para una aplicación web, por lo que no poder usarlo no es realmente un fastidio, excepto por el hecho de que necesita cambiar su código para que funcione en la web si lo está utilizando.
Por ejemplo, puede usar el paquete shared_preferences, que se basa en HTML localStorage
en la web.
Advertencias similares son válidas con respecto a las plataformas de escritorio: muy pocos complementos son compatibles con las plataformas de escritorio y, dado que este es un tema recurrente, se debe trabajar mucho más en esto en el lado del escritorio de lo que realmente se necesita en Flutter para la web.
Creación de diseños receptivos en Flutter
Debido a lo que describí anteriormente y para simplificar, supondré en el resto de esta publicación que su plataforma de destino es la web, pero los conceptos básicos también se aplican al desarrollo de escritorio.
Apoyar la web tiene beneficios y responsabilidades. Estar prácticamente obligado a admitir diferentes tamaños de pantalla puede parecer un inconveniente, pero tenga en cuenta que ejecutar la aplicación en los navegadores web le permite ver muy fácilmente cómo se verá su aplicación en pantallas de diferentes tamaños y relaciones de aspecto, sin tener que ejecutarla por separado. emuladores de dispositivos móviles.
Ahora, hablemos de código. ¿Cómo puedes hacer que tu aplicación responda?
Hay dos perspectivas desde las que se hace este análisis:
- “¿Qué widgets estoy usando o puedo usar que puedan o deban adaptarse a pantallas de diferentes tamaños?”
- "¿Cómo puedo obtener información sobre el tamaño de la pantalla y cómo puedo usarla cuando escribo código de interfaz de usuario?"
Responderemos a la primera pregunta más tarde. Hablemos primero de esto último, porque se puede tratar muy fácilmente y está en el centro del problema. Hay dos maneras de hacer esto:
- Una forma es tomar la información de
MediaQueryData
de la raíz deMediaQuery
InheritedWidget
, que debe existir en el árbol de widgets para que funcione una aplicación Flutter (es parte deMaterialApp/WidgetsApp/CupertinoApp
), que puede obtener, al igual que cualquier otroInheritedWidget
, conMediaQuery.of(context)
, que tenga una propiedadsize
, que sea de tipoSize
, y que por tanto tenga dos propiedadeswidth
yheight
del tipodouble
. - La otra forma es usar
LayoutBuilder
, que es un widget de construcción (al igual queStreamBuilder
oFutureBuilder
) que pasa a la función debuilder
(junto con elcontext
) un objetoBoxConstraints
que tiene las propiedadesminHeight
,maxHeight
,minWidth
ymaxWidth
.
Aquí hay un DartPad de ejemplo que usa MediaQuery
para obtener restricciones, cuyo código es el siguiente:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { @override Widget build(context) => Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( "Width: ${MediaQuery.of(context).size.width}", style: Theme.of(context).textTheme.headline4 ), Text( "Height: ${MediaQuery.of(context).size.height}", style: Theme.of(context).textTheme.headline4 ) ] ) ) ); }
Y aquí hay uno que usa LayoutBuilder
para lo mismo:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) => Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( "Width: ${constraints.maxWidth}", style: Theme.of(context).textTheme.headline4 ), Text( "Height: ${constraints.maxHeight}", style: Theme.of(context).textTheme.headline4 ) ] ) ) ) ); }
Ahora, pensemos en qué widgets se pueden adaptar a las restricciones.
En primer lugar, pensemos en las diferentes formas de diseñar múltiples widgets según el tamaño de la pantalla.
El widget que se adapta más fácilmente es el GridView
. De hecho, un GridView
creado con el constructor GridView.extent
ni siquiera necesita su participación para que responda, como puede ver en este ejemplo muy simple:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List elements = [ "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit" ]; @override Widget build(context) => Scaffold( body: GridView.extent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, children: elements.map((el) => Card(child: Center(child: Padding(padding: EdgeInsets.all(8.0), child: Text(el))))).toList() ) ); }
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List elements = [ "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit" ]; @override Widget build(context) => Scaffold( body: GridView.extent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, children: elements.map((el) => Card(child: Center(child: Padding(padding: EdgeInsets.all(8.0), child: Text(el))))).toList() ) ); }
Puede acomodar contenido de diferentes tamaños cambiando maxCrossAxisExtent
.
Ese ejemplo sirvió principalmente para mostrar la existencia del constructor GridView.extent
GridView
, pero una forma mucho más inteligente de hacerlo sería usar un GridView.builder
con un SliverGridDelegateWithMaxCrossAxisExtent
, en este caso donde los widgets se mostrarán en la cuadrícula se crean dinámicamente a partir de otra estructura de datos, como puede ver en este ejemplo:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => Scaffold( body: GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ) ); }
Un ejemplo de cómo GridView se adapta a diferentes pantallas es mi página de destino personal, que es una aplicación web de Flutter muy simple que consiste en un GridView
con un montón de Cards
, al igual que el código de ejemplo anterior, excepto que las Cards
son un poco más complejas y más grandes. .
Un cambio muy simple que se podría hacer a las aplicaciones diseñadas para teléfonos sería reemplazar un Drawer
con un menú permanente a la izquierda cuando haya espacio.
Por ejemplo, podríamos tener un ListView
de widgets, como el siguiente, que se usa para la navegación:
class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ) ] ); }
En un teléfono inteligente, un lugar común para usar sería dentro de un Drawer
(también conocido como menú de hamburguesas).
Las alternativas a eso serían BottomNavigationBar
o TabBar
, en combinación con TabBarView
, pero con ambos tendríamos que hacer más cambios de los necesarios con el cajón, así que nos quedaremos con el cajón.
Para mostrar solo el Drawer
que contiene el Menu
que vimos anteriormente en pantallas más pequeñas, escribiría un código que se parece al siguiente fragmento, verificando el ancho usando MediaQuery.of(context)
y pasando un objeto Drawer
a Scaffold
solo si es menos que un valor de ancho que creemos que es apropiado para nuestra aplicación:
Scaffold( appBar: AppBar(/* ... \*/), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: /* ... \*/ )
Ahora, pensemos en el body
del Scaffold
. Como contenido principal de muestra de nuestra aplicación, usaremos GridView
que creamos anteriormente, que mantenemos en un widget separado llamado Content
para evitar confusiones:
class Content extends StatelessWidget { final List elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }
class Content extends StatelessWidget { final List elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }
En pantallas más grandes, el cuerpo en sí puede ser una Row
que muestra dos widgets: el Menu
, que está restringido a un ancho fijo, y el Content
ocupa el resto de la pantalla.
En pantallas más pequeñas, todo el body
sería el Content
.
Envolveremos todo en un SafeArea
y un Center
widget porque a veces los widgets de la aplicación web Flutter, especialmente cuando se usan Row
s y Column
s, terminan fuera del área de pantalla visible, y eso se soluciona con SafeArea
y/o Center
.
Esto significa que el body
del Scaffold
será el siguiente:
SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) )
Aquí está todo eso junto:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: HomePage() ); } class HomePage extends StatelessWidget { @override Widget build(context) => Scaffold( appBar: AppBar(title: Text("test")), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) ) ); } class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ) ] ); } class Content extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }
Esta es la mayoría de las cosas que necesitará como introducción general a la IU receptiva en Flutter. Gran parte de su aplicación dependerá de la interfaz de usuario específica de su aplicación, y es difícil determinar exactamente qué puede hacer para que su aplicación responda, y puede tomar muchos enfoques según sus preferencias. Ahora, sin embargo, veamos cómo podemos convertir un ejemplo más completo en una aplicación receptiva, pensando en los elementos comunes de la aplicación y los flujos de la interfaz de usuario.
Ponerlo en contexto: hacer que una aplicación responda
Hasta ahora, solo tenemos una pantalla. ¡Expandámoslo en una aplicación de dos pantallas con navegación basada en URL!
Creación de una página de inicio de sesión receptiva
Lo más probable es que su aplicación tenga una página de inicio de sesión. ¿Cómo podemos hacer que responda?
Las pantallas de inicio de sesión en los dispositivos móviles suelen ser bastante similares entre sí. El espacio disponible no es mucho; por lo general, es solo una Column
con algo de Padding
alrededor de sus widgets, y contiene TextField
de texto para escribir un nombre de usuario y una contraseña y un botón para iniciar sesión. Por lo tanto, es bastante estándar (aunque no funciona, ya que eso requeriría, entre otras cosas , un TextEditingController
para cada TextField
) página de inicio de sesión para una aplicación móvil podría ser el siguiente:
Scaffold( body: Container( padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () {} ) ] ), ), )
Se ve bien en un dispositivo móvil, pero esos campos de texto muy TextField
comienzan a verse discordantes en una tableta, y mucho menos en una pantalla más grande. Sin embargo, no podemos simplemente decidir sobre un ancho fijo porque los teléfonos tienen diferentes tamaños de pantalla y debemos mantener un grado de flexibilidad.
Por ejemplo, a través de la experimentación, podríamos encontrar que el ancho máximo debería ser 500. Bueno, estableceríamos las constraints
de Container
en 500 (utilicé Container
en lugar de Padding
en el ejemplo anterior porque sabía a dónde iba con este ) y estamos listos para ir, ¿verdad? Realmente no, porque eso haría que los widgets de inicio de sesión se pegaran al lado izquierdo de la pantalla, lo que podría ser incluso peor que estirar todo. Entonces, envolvemos un widget de Center
, como este:
Center( child: Container( constraints: BoxConstraints(maxWidth: 500), padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), child: Column(/* ... \*/) ) )
Eso ya se ve bien, y ni siquiera hemos tenido que usar LayoutBuilder
o MediaQuery.of(context).size
. Sin embargo, vayamos un paso más allá para que esto se vea muy bien. En mi opinión, se vería mejor si la parte del primer plano estuviera de alguna manera separada del fondo. Podemos lograrlo dando un color de fondo a lo que está detrás del Container
con los widgets de entrada y manteniendo el Container
de primer plano en blanco. Para que se vea un poco mejor, evitemos que el Container
se estire hacia la parte superior e inferior de la pantalla en dispositivos grandes, démosle esquinas redondeadas y démosle una agradable transición animada entre los dos diseños.
Todo eso ahora requiere un LayoutBuilder
y un Container
externo para establecer un color de fondo y agregar relleno alrededor del Container
y no solo en los lados solo en pantallas más grandes. Además, para hacer que el cambio en la cantidad de relleno sea animado, solo necesitamos convertir ese Container
externo en un AnimatedContainer
, lo que requiere una duration
para la animación, que estableceremos en medio segundo, que es Duration(milliseconds: 500)
en código.
Este es un ejemplo de una página de inicio de sesión receptiva:
class LoginPage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) { return AnimatedContainer( duration: Duration(milliseconds: 500), color: Colors.lightGreen[200], padding: constraints.maxWidth < 500 ? EdgeInsets.zero : EdgeInsets.all(30.0), child: Center( child: Container( padding: EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), constraints: BoxConstraints( maxWidth: 500, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () { Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => HomePage() ) ); } ) ] ), ), ) ); } ) ); }
Como puede ver, también he cambiado RaisedButton
de onPressed
a una devolución de llamada que nos lleva a una pantalla llamada HomePage
(que podría ser, por ejemplo, la vista que construimos previamente con GridView
y un menú o un cajón). Ahora, sin embargo, esa parte de la navegación es en lo que nos vamos a centrar.
Rutas con nombre: hacer que la navegación de su aplicación se asemeje más a una aplicación web adecuada
Una cosa común que tienen las aplicaciones web es la capacidad de cambiar las pantallas según la URL. Por ejemplo, ir a https://appurl/login
debería darte algo diferente a https://appurl/somethingelse
. Flutter, de hecho, admite rutas con nombre , que tienen dos propósitos:
- En una aplicación web, tienen exactamente esa característica que mencioné en la oración anterior.
- En cualquier aplicación, te permiten predefinir rutas para tu aplicación y darles nombres, y luego poder navegar hacia ellas solo especificando su nombre.
Para hacer eso, necesitamos cambiar el constructor de MaterialApp
a uno que se parezca a lo siguiente:
MaterialApp( initialRoute: "/login", routes: { "/login": (context) => LoginPage(), "/home": (context) => HomePage() } );
Y luego podemos cambiar a una ruta diferente usando Navigator.pushNamed(context, routeName)
y Navigator.pushReplacementNamed(context, routeName)
, en lugar de Navigator.push(context, route)
y Navigator.pushReplacement(context, route)
.
Esto es lo que se aplica a la aplicación hipotética que construimos en el resto de este artículo. Realmente no puedes ver las rutas con nombre en acción en DartPad, así que deberías probar esto en tu propia máquina con flutter run
, o ver el ejemplo en acción:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( initialRoute: "/login", routes: { "/login": (context) => LoginPage(), "/home": (context) => HomePage() } ); } class LoginPage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) { return AnimatedContainer( duration: Duration(milliseconds: 500), color: Colors.lightGreen[200], padding: constraints.maxWidth < 500 ? EdgeInsets.zero : const EdgeInsets.all(30.0), child: Center( child: Container( padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), constraints: BoxConstraints( maxWidth: 500, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () { Navigator.pushReplacementNamed( context, "/home" ); } ) ] ), ), ) ); } ) ); } class HomePage extends StatelessWidget { @override Widget build(context) => Scaffold( appBar: AppBar(title: Text("test")), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) ) ); } class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ), FlatButton( onPressed: () {Navigator.pushReplacementNamed( context, "/login");}, child: ListTile( leading: Icon(Icons.exit_to_app), title: Text("Log Out"), ) ) ] ); } class Content extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }
Adelante con tu aventura Flutter
Eso debería darle una idea de lo que puede hacer con Flutter en pantallas más grandes, específicamente en la web. Es un framework encantador, muy fácil de usar, y su soporte multiplataforma extremo solo lo hace más esencial para aprender y comenzar a usar. Entonces, ¡anímate y comienza a confiar también en Flutter para aplicaciones web!
Más recursos
- "Conchas de escritorio", GitHub
El estado actual y siempre actualizado de Flutter en el escritorio - “Soporte de escritorio para Flutter”, Flutter
Información sobre las plataformas de escritorio totalmente compatibles - “Soporte web para Flutter”, Flutter
Información sobre Flutter para la web - “Todas las muestras”, Flutter
Una lista seleccionada de muestras y aplicaciones de Flutter