Uso de Flutter de Google para un desarrollo móvil verdaderamente multiplataforma

Publicado: 2022-03-10
Resumen rápido ↬ Flutter hace que crear aplicaciones móviles multiplataforma sea pan comido. Este artículo presenta Flutter, lo compara con otras plataformas de desarrollo móvil y muestra cómo usarlo para comenzar a crear aplicaciones.

Flutter 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.

¡Más después del salto! Continúe leyendo a continuación ↓

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.

Nueva plantilla de aplicación Flutter
La nueva aplicación Flutter que se ejecuta en iOS y Android. (Vista previa grande)

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.

Herramientas de aleteo
Las herramientas Flutter admiten una variedad de entornos. (Vista previa grande)

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:

Aplicación mínima de aleteo
Aplicación Minimal Flutter con un solo contenedor (vista previa grande)

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.

Cambio de estado
Manejo de un cambio de estado de la interacción del usuario (vista previa grande)

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), ], ); } } 
Widget de fila
Widget de fila que muestra el diseño horizontal (vista previa grande)

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 ), ), ); } } 
Fondo degradado
Pintar un fondo degradado (vista previa grande)

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:

Animación
Animación de tamaño de icono

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