Uso de Flutter de Google para un desarrollo móvil verdaderamente multiplataforma
Publicado: 2022-03-10Flutter es un marco de desarrollo móvil multiplataforma de código abierto de Google. Permite crear hermosas aplicaciones de alto rendimiento para iOS y Android a partir de una única base de código. También es la plataforma de desarrollo para el próximo sistema operativo Fuchsia de Google. Además, está diseñado de manera que se puede llevar a otras plataformas, a través de integradores de motores Flutter personalizados.
Por qué se creó Flutter y por qué debería usarlo
Históricamente, los kits de herramientas multiplataforma han adoptado uno de dos enfoques:
- Envuelven una vista web en una aplicación nativa y construyen la aplicación como si fuera un sitio web.
- Envuelven los controles nativos de la plataforma y proporcionan algo de abstracción multiplataforma sobre ellos.
Flutter adopta un enfoque diferente en un intento por mejorar el desarrollo móvil. Proporciona un marco contra el que trabajan los desarrolladores de aplicaciones y un motor con un tiempo de ejecución portátil para alojar aplicaciones. El marco se basa en la biblioteca de gráficos de Skia y proporciona widgets que realmente se procesan, en lugar de ser solo envoltorios en los controles nativos.
Este enfoque brinda la flexibilidad de crear una aplicación multiplataforma de una manera completamente personalizada, como lo proporciona la opción de envoltura web, pero al mismo tiempo ofrece un rendimiento fluido. Mientras tanto, la rica biblioteca de widgets que viene con Flutter, junto con una gran cantidad de widgets de código abierto, la convierte en una plataforma rica en funciones para trabajar. En pocas palabras, Flutter es lo más parecido que han tenido los desarrolladores móviles para el desarrollo multiplataforma con poco o ningún compromiso.
Dardo
Las aplicaciones de Flutter están escritas en Dart, que es un lenguaje de programación desarrollado originalmente por Google. Dart es un lenguaje orientado a objetos que admite la compilación anticipada y justo a tiempo, lo que lo hace ideal para crear aplicaciones nativas, al mismo tiempo que proporciona un flujo de trabajo de desarrollo eficiente con la recarga en caliente de Flutter. Flutter también se mudó recientemente a la versión 2.0 de Dart.
El lenguaje Dart ofrece muchas de las funciones que se ven en otros lenguajes, incluida la recolección de elementos no utilizados, la espera asíncrona, la tipificación fuerte, los genéricos y una rica biblioteca estándar.
Dart ofrece una intersección de funciones que deberían ser familiares para los desarrolladores que provienen de una variedad de lenguajes, como C#, JavaScript, F#, Swift y Java. Además, Dart puede compilar a Javascript. Combinado con Flutter, esto permite que el código se comparta en plataformas web y móviles.
Cronología histórica de eventos
- abril 2015
Flutter (originalmente con el nombre en código Sky) mostrado en Dart Developer Summit - noviembre 2015
Cielo renombrado a Flutter - febrero 2018
Flutter beta 1 anunciado en el Mobile World Congress 2018 - abril 2018
Se anuncia la versión beta 2 de Flutter - mayo 2018
Flutter beta 3 anunciado en Google I/O. Google anuncia que Flutter está listo para aplicaciones de producción
Comparación con otras plataformas de desarrollo
Nativo de Apple/Android
Las aplicaciones nativas ofrecen la menor fricción en la adopción de nuevas funciones. Tienden a tener experiencias de usuario más en sintonía con la plataforma dada, ya que las aplicaciones se crean utilizando controles de los propios proveedores de plataformas (Apple o Google) y, a menudo, siguen las pautas de diseño establecidas por estos proveedores. En la mayoría de los casos, las aplicaciones nativas funcionarán mejor que las creadas con ofertas multiplataforma, aunque la diferencia puede ser insignificante en muchos casos dependiendo de la tecnología multiplataforma subyacente.
Una gran ventaja que tienen las aplicaciones nativas es que pueden adoptar tecnologías completamente nuevas que Apple y Google crean en versión beta inmediatamente si lo desean, sin tener que esperar a ninguna integración de terceros. La principal desventaja de crear aplicaciones nativas es la falta de reutilización de código entre plataformas, lo que puede hacer que el desarrollo sea costoso si se dirige a iOS y Android.
reaccionar nativo
React Native permite crear aplicaciones nativas usando JavaScript. Los controles reales que usa la aplicación son controles de plataforma nativos, por lo que el usuario final tiene la sensación de una aplicación nativa. Para las aplicaciones que requieren una personalización más allá de lo que proporciona la abstracción de React Native, aún podría ser necesario el desarrollo nativo. En los casos en que la cantidad de personalización requerida es sustancial, el beneficio de trabajar dentro de la capa de abstracción de React Native disminuye hasta el punto en que, en algunos casos, desarrollar la aplicación de forma nativa sería más beneficioso.
Xamarin
Cuando se habla de Xamarin, hay dos enfoques diferentes que deben evaluarse. Para su enfoque más multiplataforma, está Xamarin.Forms. Aunque la tecnología es muy diferente a React Native, conceptualmente ofrece un enfoque similar en el sentido de que abstrae los controles nativos. Asimismo, tiene inconvenientes similares con respecto a la personalización.
En segundo lugar, está lo que muchos denominan Xamarin-clásico. Este enfoque usa los productos iOS y Android de Xamarin de forma independiente para crear características específicas de la plataforma, como cuando se usa directamente Apple/Android nativo, solo con C# o F# en el caso de Xamarin. El beneficio con Xamarin es que se puede compartir código no específico de la plataforma, cosas como redes, acceso a datos, servicios web, etc.
A diferencia de estas alternativas, Flutter intenta brindar a los desarrolladores una solución multiplataforma más completa, con reutilización de código, interfaces de usuario fluidas y de alto rendimiento y herramientas excelentes.
Descripción general de una aplicación Flutter
Crear una aplicación
Después de instalar Flutter, crear una aplicación con Flutter es tan simple como abrir una línea de comando e ingresar flutter create [app_name]
, seleccionar el comando "Flutter: New Project" en VS Code o seleccionar "Start a new Flutter Project" en Android Estudio o IntelliJ.
Independientemente de si elige usar un IDE o la línea de comandos junto con su editor preferido, la nueva plantilla de aplicación Flutter le brinda un buen punto de partida para una aplicación.
La aplicación trae el paquete flutter
/ material.dart
para ofrecer algunos andamios básicos para la aplicación, como una barra de título, íconos de materiales y temas. También configura un widget con estado para demostrar cómo actualizar la interfaz de usuario cuando cambia el estado de la aplicación.
Opciones de herramientas
Flutter ofrece una flexibilidad increíble con respecto a las herramientas. Las aplicaciones se pueden desarrollar fácilmente desde la línea de comandos junto con cualquier editor, como desde un IDE compatible como VS Code, Android Studio o IntelliJ. El enfoque a seguir depende en gran medida de la preferencia del desarrollador.
Android Studio ofrece la mayoría de las funciones, como Flutter Inspector para analizar los widgets de una aplicación en ejecución y monitorear el rendimiento de la aplicación. También ofrece varias refactorizaciones que son convenientes cuando se desarrolla una jerarquía de widgets.
VS Code ofrece una experiencia de desarrollo más ligera, ya que tiende a iniciarse más rápido que Android Studio/IntelliJ. Cada IDE ofrece asistentes de edición incorporados, como la finalización de código, lo que permite la exploración de varias API, así como un buen soporte de depuración.
La línea de comandos también es compatible con el comando flutter
, lo que facilita la creación, actualización y ejecución de una aplicación sin ninguna otra dependencia de herramientas más allá de un editor.
Recarga en caliente
Independientemente de las herramientas, Flutter mantiene un excelente soporte para la recarga en caliente de una aplicación. Esto permite modificar una aplicación en ejecución en muchos casos, manteniendo el estado, sin tener que detener la aplicación, reconstruirla y volver a implementarla.
La recarga en caliente aumenta drásticamente la eficiencia del desarrollo al permitir una iteración más rápida. Realmente hace que sea un placer trabajar con la plataforma.
Pruebas
Flutter incluye una utilidad WidgetTester
para interactuar con los widgets de una prueba. La nueva plantilla de aplicación incluye una prueba de muestra para demostrar cómo usarla al crear una prueba, como se muestra a continuación:
// Test included with the new Flutter application template import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:myapp/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(new MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); }
Uso de paquetes y complementos
Flutter apenas está comenzando, pero ya existe un rico ecosistema de desarrolladores: una gran cantidad de paquetes y complementos ya están disponibles para los desarrolladores.
Para agregar un paquete o complemento, simplemente incluya la dependencia en el archivo pubspec.yaml en el directorio raíz de la aplicación. Luego, ejecute los flutter packages get
desde la línea de comando o a través del IDE, y las herramientas de Flutter traerán todas las dependencias requeridas.
Por ejemplo, para usar el popular complemento de selección de imágenes para Flutter, pubspec.yaml solo necesita enumerarlo como una dependencia de esta manera:
dependencies: image_picker: "^0.4.1"
Luego, la ejecución de flutter packages get
trae todo lo que necesita para usarlo, después de lo cual se puede importar y usar en Dart:
import 'package:image_picker/image_picker.dart';
Widgets
Todo en Flutter es un widget. Esto incluye elementos de la interfaz de usuario, como ListView
, TextBox
e Image
, así como otras partes del marco, incluido el diseño, la animación, el reconocimiento de gestos y los temas, por nombrar solo algunos.
Al hacer que todo sea un widget, toda la aplicación, que por cierto también es un widget, se puede representar dentro de la jerarquía de widgets. Tener una arquitectura donde todo es un widget deja en claro de dónde provienen ciertos atributos y comportamientos aplicados a una parte de una aplicación. Esto es diferente de la mayoría de los otros marcos de aplicación, que asocian propiedades y comportamiento de manera inconsistente, a veces anexándolos de otros componentes en una jerarquía y otras veces en el propio control.
Ejemplo de widget de interfaz de usuario simple
El punto de entrada a una aplicación de Flutter es la función principal. Para poner un widget para un elemento de interfaz de usuario en la pantalla, en main()
llame a runApp()
y pásele el widget que servirá como la raíz de la jerarquía de widgets.
import 'package:flutter/material.dart'; void main() { runApp( Container(color: Colors.lightBlue) ); }
Esto da como resultado un widget de Container
azul claro que llena la pantalla:
Widgets sin estado frente a con estado
Los widgets vienen en dos sabores: sin estado y con estado. Los widgets sin estado no cambian su contenido después de que se crean e inicializan, mientras que los widgets con estado mantienen algún estado que puede cambiar mientras se ejecuta la aplicación, por ejemplo, en respuesta a la interacción del usuario.
En este ejemplo, un widget FlatButton
y un widget de Text
se dibujan en la pantalla. El widget de Text
comienza con una String
predeterminada para su estado. Al presionar el botón, se produce un cambio de estado que hará que el widget de Text
se actualice y muestre una nueva String
.
Para encapsular un widget, cree una clase que derive de StatelessWidget
o StatefulWidget
. Por ejemplo, el Container
azul claro podría escribirse de la siguiente manera:
class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Container(color: Colors.lightBlue); } }
Flutter llamará al método de compilación del widget cuando se inserte en el árbol de widgets para que esta parte de la interfaz de usuario se pueda representar.
Para un widget con estado, derive de StatefulWidget
:
class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } }
class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } }
Los widgets con estado devuelven una clase State
que es responsable de construir el árbol de widgets para un estado dado. Cuando cambia el estado, se reconstruye la parte asociada del árbol de widgets.
En el siguiente código, la clase State
actualiza una String
cuando se hace clic en un botón:
class MyWidgetState extends State { String text = "some text"; @override Widget build(BuildContext context) { return Container( color: Colors.lightBlue, child: Padding( padding: const EdgeInsets.all(50.0), child: Directionality( textDirection: TextDirection.ltr, child: Column( children: [ FlatButton( child: Text('Set State'), onPressed: () { setState(() { text = "some new text"; }); }, ), Text( text, style: TextStyle(fontSize: 20.0)), ], ) ) ) ); } }
class MyWidgetState extends State { String text = "some text"; @override Widget build(BuildContext context) { return Container( color: Colors.lightBlue, child: Padding( padding: const EdgeInsets.all(50.0), child: Directionality( textDirection: TextDirection.ltr, child: Column( children: [ FlatButton( child: Text('Set State'), onPressed: () { setState(() { text = "some new text"; }); }, ), Text( text, style: TextStyle(fontSize: 20.0)), ], ) ) ) ); } }
El estado se actualiza en una función que se pasa a setState()
. Cuando se llama setState()
, esta función puede establecer cualquier estado interno, como la cadena en este ejemplo. Luego, se llamará al método de build
, actualizando el árbol del widget con estado.
Tenga en cuenta también el uso del widget de Directionality
para establecer la dirección del texto para cualquier widget en su subárbol que lo requiera, como los widgets de Text
. Los ejemplos aquí están creando código desde cero, por lo que se necesita Directionality
en algún lugar de la jerarquía de widgets. Sin embargo, usar el widget MaterialApp
, como con la plantilla de aplicación predeterminada, configura la dirección del texto implícitamente.
Disposición
La función runApp
infla el widget para llenar la pantalla de forma predeterminada. Para controlar el diseño del widget, Flutter ofrece una variedad de widgets de diseño. Hay widgets para realizar diseños que alinean los widgets secundarios vertical u horizontalmente, expanden los widgets para llenar un espacio particular, limitan los widgets a un área determinada, los centran en la pantalla y permiten que los widgets se superpongan entre sí.
Dos widgets de uso común son Row
y Column
. Estos widgets realizan diseños para mostrar sus widgets secundarios horizontalmente (Fila) o verticalmente (Columna).
El uso de estos widgets de diseño simplemente implica envolverlos en una lista de widgets secundarios. mainAxisAlignment
controla cómo se colocan los widgets a lo largo del eje de diseño, ya sea centrado, al principio, al final o con varias opciones de espaciado.
El siguiente código muestra cómo alinear varios widgets secundarios en una Row
o Column
:
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Row( //change to Column for vertical layout mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.android, size: 30.0), Icon(Icons.pets, size: 10.0), Icon(Icons.stars, size: 75.0), Icon(Icons.rowing, size: 25.0), ], ); } }
Respondiendo al tacto
La interacción táctil se maneja con gestos, que están encapsulados en la clase GestureDetector
. Dado que también es un widget, agregar el reconocimiento de gestos es tan fácil como envolver widgets secundarios en un GestureDetector
.
Por ejemplo, para agregar manejo táctil a un Icon
, conviértalo en un elemento secundario de GestureDetector
y configure los controladores del detector para capturar los gestos deseados.
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( onTap: () => print('you tapped the star'), onDoubleTap: () => print('you double tapped the star'), onLongPress: () => print('you long pressed the star'), child: Icon(Icons.stars, size: 200.0), ); } }
En este caso, cuando se realiza un toque, doble toque o presión prolongada sobre el icono, se imprime el texto asociado:
To hot reload your app on the fly, press "r". To restart the app entirely, press "R". An Observatory debugger and profiler on iPhone X is available at: https://127.0.0.1:8100/ For a more detailed help message, press "h". To quit, press "q". flutter: you tapped the star flutter: you double tapped the star flutter: you long pressed the star
Además de los simples gestos de toque, hay una gran cantidad de reconocedores, para todo, desde paneo y escala, hasta arrastrar. Esto hace que sea muy fácil crear aplicaciones interactivas.
Cuadro
Flutter también ofrece una variedad de widgets para pintar, incluidos los que modifican la opacidad, establecen rutas de recorte y aplican decoraciones. Incluso es compatible con la pintura personalizada a través del widget CustomPaint
y las clases CustomPainter
y Canvas
asociadas.
Un ejemplo de widget de pintura es DecoratedBox
, que puede pintar una BoxDecoration
en la pantalla. El siguiente ejemplo muestra cómo usar esto para llenar la pantalla con un relleno degradado:
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { return new DecoratedBox( child: Icon(Icons.stars, size: 200.0), decoration: new BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } }
Animación
Flutter incluye una clase AnimationController
que controla la reproducción de la animación a lo largo del tiempo, incluido el inicio y la detención de una animación, así como la variación de los valores de una animación. Además, hay un widget AnimatedBuilder
que permite construir una animación junto con un AnimationController
.
Cualquier widget, como la estrella decorada que se mostró anteriormente, puede tener sus propiedades animadas. Por ejemplo, refactorizar el código a un StatefulWidget
, ya que la animación es un cambio de estado, y pasar un AnimationController
a la clase State
permite usar el valor animado a medida que se construye el widget.
class StarWidget extends StatefulWidget { @override State createState() { return StarState(); } } class StarState extends State with SingleTickerProviderStateMixin { AnimationController _ac; final double _starSize = 300.0; @override void initState() { super.initState(); _ac = new AnimationController( duration: Duration(milliseconds: 750), vsync: this, ); _ac.forward(); } @override Widget build(BuildContext context) { return new AnimatedBuilder( animation: _ac, builder: (BuildContext context, Widget child) { return DecoratedBox( child: Icon(Icons.stars, size: _ac.value * _starSize), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } ); } }
class StarWidget extends StatefulWidget { @override State createState() { return StarState(); } } class StarState extends State with SingleTickerProviderStateMixin { AnimationController _ac; final double _starSize = 300.0; @override void initState() { super.initState(); _ac = new AnimationController( duration: Duration(milliseconds: 750), vsync: this, ); _ac.forward(); } @override Widget build(BuildContext context) { return new AnimatedBuilder( animation: _ac, builder: (BuildContext context, Widget child) { return DecoratedBox( child: Icon(Icons.stars, size: _ac.value * _starSize), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } ); } }
class StarWidget extends StatefulWidget { @override State createState() { return StarState(); } } class StarState extends State with SingleTickerProviderStateMixin { AnimationController _ac; final double _starSize = 300.0; @override void initState() { super.initState(); _ac = new AnimationController( duration: Duration(milliseconds: 750), vsync: this, ); _ac.forward(); } @override Widget build(BuildContext context) { return new AnimatedBuilder( animation: _ac, builder: (BuildContext context, Widget child) { return DecoratedBox( child: Icon(Icons.stars, size: _ac.value * _starSize), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } ); } }
En este caso, el valor se usa para variar el tamaño del widget. La función builder
se llama cada vez que cambia el valor animado, lo que hace que el tamaño de la estrella varíe más de 750 ms, creando una escala en efecto:
Uso de funciones nativas
Canales de plataforma
Para brindar acceso a las API de la plataforma nativa en Android e iOS, una aplicación de Flutter puede usar los canales de la plataforma. Estos permiten que el código Flutter Dart envíe mensajes a la aplicación de alojamiento iOS o Android. Muchos de los complementos de código abierto que están disponibles se construyen utilizando mensajes a través de canales de plataforma. Para aprender a trabajar con canales de plataforma, la documentación de Flutter incluye un buen documento que demuestra cómo acceder a las API de batería nativas.
Conclusión
Incluso en versión beta, Flutter ofrece una excelente solución para crear aplicaciones multiplataforma. Con sus excelentes herramientas y recarga en caliente, brinda una experiencia de desarrollo muy agradable. La gran cantidad de paquetes de código abierto y la excelente documentación facilitan el comienzo. De cara al futuro, los desarrolladores de Flutter podrán apuntar a Fuchsia además de iOS y Android. Teniendo en cuenta la extensibilidad de la arquitectura del motor, no me sorprendería ver a Flutter aterrizar también en una variedad de otras plataformas. Con una comunidad en crecimiento, es un buen momento para participar.
Próximos pasos
- instalar aleteo
- Tour de lenguaje de dardos
- Laboratorios de código de Flutter
- Curso Flutter Udacity
- Código fuente del artículo