Cómo hice una aplicación de escritorio en Flutter (+ bonus)

Recientemente, me llegó la noticia de que se lanzó el próximo lanzamiento de Flutter (1.9) , que promete varios beneficios, incluido el soporte temprano para aplicaciones web.

En el trabajo, estoy desarrollando aplicaciones móviles en React Native, pero miro a Flutter con curiosidad. Para aquellos que no están al tanto: en Flutter ya puede crear aplicaciones para Android e iOS, se está preparando el soporte para aplicaciones web para su lanzamiento, y también hay planes para admitir el escritorio.

Tal es "un anillo para gobernar todo".

Después de pensar un par de días sobre qué tipo de aplicación puede intentar hacer, decidí elegir la tarea con un asterisco: ¿qué necesitamos para estos caminos trillados? ¡Pásate por el escritorio y supera heroicamente las dificultades! Mirando hacia el futuro, diré que casi no surgieron dificultades.

Under the cut: una historia sobre cómo resolví las tareas habituales del programador React Native usando las herramientas Flutter, más la impresión general de la tecnología.



Pensando en las características de Flutter que me gustaría "tocar", decidí que en mi aplicación debería estar:

  • solicitudes a la API remota;
  • transiciones entre pantallas;
  • Animaciones de transición
  • gerente estatal - redux o algo similar.

No sé cómo hacer back-end, así que decidí buscar una API abierta de terceros. Como resultado, me decidí por este recurso: cursos CBR en XML y JSON, API . Bueno, aquí finalmente he decidido la funcionalidad de la aplicación: habrá dos pantallas, en la principal hay una lista de monedas al tipo de cambio CBR, cuando haces clic en un elemento de la lista, abrimos una pantalla con información detallada.

Preparación


Dado que el comando flutter create aún no sabe cómo crear un proyecto para Windows / Linux (actualmente solo se admite Mac, use el indicador --macos para esto), debe usar este repositorio, donde hay un ejemplo preparado. Clonamos el repositorio, tomamos la carpeta de example desde allí, si es necesario, cambia el nombre y continuamos trabajando en él.

Dado que el soporte para plataformas de escritorio todavía está en desarrollo, aún debe realizar una serie de manipulaciones. Para acceder a las funciones en desarrollo, ejecute en la terminal:

 flutter channel master flutter upgrade 

Además, debe decirle a Flutter que puede usar su plataforma:

 flutter config --enable-linux-desktop 

o

 flutter config --enable-macos-desktop 

o

 flutter config --enable-windows-desktop 

Si todo salió bien, entonces ejecutando el comando flutter doctor debería ver una salida similar:



Entonces, el escenario está listo, la audiencia en el pasillo, podemos comenzar.

Diseño


Lo primero que llama la atención después de React Native es la falta de un lenguaje de marcado especial a la JSX. Flutter te obliga a escribir tanto el marcado como la lógica de negocios en Dart . Al principio, esto es molesto: el aspecto no tiene nada que ver, el código parece engorroso, ¡e incluso estos paréntesis están al final del componente!

Por ejemplo, tales:



¡Y este no es el límite! Vale la pena eliminar uno en el lugar equivocado y se le garantiza un pasatiempo agradable (no).

Además, debido a las peculiaridades de los componentes de estilo en Flutter, para componentes grandes, la sangría desde el borde izquierdo del editor aumenta con bastante rapidez, y con ello el número de paréntesis para cerrar.

Esta característica es que en Flutter los estilos son los mismos componentes (para ser más precisos: widgets).

Si en React Native organizamos tres botones en una fila dentro de la View para que distribuyan uniformemente el espacio del contenedor, es suficiente para mí especificar flexDirection: 'row' para la View en estilos y agregar flex: 1 para los botones en los estilos, entonces Flutter tiene un componente separado Row para organizar elementos en una fila y una separada para "expandibilidad" de un elemento a todo el espacio disponible: Expanded .

Como resultado, en lugar de

 <View style={{height: 100, width:300, flexDirection: 'row'}}> <Button title='A' style={{flex:1}}> <Button title='B' style={{flex:1}}> <Button title='C' style={{flex:1}}> </View> 

tenemos que escribir así:

 Container( height: 100, width: 300, child: Row( children: <Widget>[ Expanded( child: RaisedButton( onPressed: () {}, child: Text('A'), ), ), Expanded( child: RaisedButton( onPressed: () {}, child: Text('B'), ), ), Expanded( child: RaisedButton( onPressed: () {}, child: Text('C'), ), ), ], ), ) 

Más detallado, ¿no?

O, por ejemplo, desea agregar un marco con bordes redondeados a este contenedor. En React Native, simplemente agregamos a los estilos:

 borderRadius: 5, borderWidth: 1, borderColor: '#ccc' 

En Flutter, tenemos que agregar algo como esto a los argumentos del contenedor:

 decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(5)), border: Border.all(width: 1, color: Color(0xffcccccc)) ), 

En general, al principio, mi marcado se convirtió en enormes hojas de código, en las que el diablo se rompería la pierna. Sin embargo, no todo es tan malo.

Primero, los componentes grandes deben, por supuesto, desglosarse, colocarse en widgets separados o al menos en los métodos de su clase de widgets.

En segundo lugar, el complemento Flutter en VS Code ayuda mucho: en la imagen anterior, los comentarios entre paréntesis están firmados por el complemento en sí (y son indelebles), lo que ayuda a no confundirse entre paréntesis. Además de herramientas de formateo automático: después de media hora te acostumbras a presionar periódicamente Ctrl+Shift+I para formatear el código.

Además, la sintaxis del lenguaje Dart en la segunda edición se volvió mucho más agradable, por lo que al final del día ya disfrutaba de usarlo. Inusual? Si Pero no es desagradable.

Solicitudes API


En React Native, para obtener datos de alguna API, generalmente usamos el método fetch , que nos devuelve Promise .

En Flutter, la situación es similar. Después de mirar los ejemplos en la documentación, agregué el paquete http a pubspec.yaml (un análogo de package.json del mundo JS) y escribí algo como esto:

 Future<http.Response> getAnything() { return http.get(URL); } 

El objeto Future es muy similar en significado a Promise, por lo que todo es bastante transparente aquí. Bueno, para serializar / deserializar objetos json, puede usar el concepto de clases de modelo con métodos especiales de fromJSON / toJSON . Puede leer más sobre esto en la documentación .

Transición entre pantallas


A pesar de que estaba haciendo una aplicación de escritorio, desde el punto de vista de Flutter no hay diferencia en qué plataforma está girando. Bueno, es decir, en mi caso esto es así, en general, no lo sé. De hecho, la ventana del sistema en la que se inicia la aplicación flutter es la misma pantalla de un teléfono inteligente.

La transición entre pantallas es bastante trivial: creamos una clase de widget de pantalla y luego usamos la clase estándar Navigator .

En el caso más simple, podría verse así:

 RaisedButton( child: Text('Go to Detail'), onPressed: () { Navigator.of(context).push<void>(MaterialPageRoute(builder: (context) => DetailScreen())); }, ) 

Si su aplicación tiene varias pantallas, es más razonable primero preparar un diccionario de rutas y luego usar el método pushNamed . Un pequeño ejemplo de la documentación:

 class NavigationApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( ... routes: <String, WidgetBuilder>{ '/a': (BuildContext context) => usualNavscreen(), '/b': (BuildContext context) => drawerNavscreen(), } ... ); } } // AnyWidget ... onPressed: () { Navigator.of(context).pushNamed('/a'); }, ... 

Además, puede preparar una animación especial para cambiar entre pantallas y escribir algo como esto:

 Navigator.of(context).push<void>(ScaleRoute(page: DetailScreen())); 

Aquí ScaleRoute es una clase especial para crear animaciones de transición. Buenos ejemplos de tales animaciones se pueden encontrar aquí .

Gestión del estado


Ocurre que necesitamos tener acceso a algunos datos desde cualquier parte de nuestra aplicación. En React Native, redux usa a menudo (si no con mayor frecuencia) para estos fines.

Para Flutter, hay un repositorio que da ejemplos del uso de varias arquitecturas de aplicaciones: hay Redux, MVC y MVU, e incluso aquellas de las que no he oído hablar antes.

Habiendo rebuscado un poco en estos ejemplos, decidí detenerme en Provider .

En general, la idea es bastante simple: creamos una clase especial que ChangeNotifier clase ChangeNotifier , en la que almacenaremos nuestros datos, los actualizaremos utilizando los métodos de esta clase y los recogeremos de allí si es necesario. Consulte la documentación del paquete para más detalles.

Para hacer esto, agregue el paquete del provider a pubspec.yaml y prepare la clase Proveedor. En mi caso, se ve así:

 import 'package:flutter/material.dart'; import 'package:rates_app/models/rate.dart'; class RateProvider extends ChangeNotifier { Rate currentrate; void setCurrentRate(Rate rate) { this.currentrate = rate; notifyListeners(); } } 

Aquí Rate es mi clase de modelo de moneda (con los campos name , code , value , etc.), currentrate es el campo en el que se almacenará la moneda seleccionada, y setCurrentRate es el método por el cual se currentrate valor de la currentrate .

Para adjuntar nuestro proveedor a la aplicación, cambiamos el código de clase de la aplicación:

 @override Widget build(BuildContext context) { return ChangeNotifierProvider( builder: (context) => RateProvider(), //   child: MaterialApp( ... ), home: HomeScreen(), ), ); } 

Eso es todo, ahora si queremos guardar la moneda seleccionada, entonces escribimos algo como esto:

 Provider.of<RateProvider>(context).setCurrentRate(rate); 

Y si queremos obtener el valor almacenado, entonces esto:

 var rate = Provider.of<RateProvider>(context).currentrate; 

Todo es bastante transparente y no hay repeticiones (a diferencia de Redux). Por supuesto, quizás para aplicaciones más complejas todo saldrá no tan bien, sino para aquellos como mi ejemplo, los vinos puros.

Construir aplicación


En teoría, el comando flutter build <platform> se usa para construir la aplicación. En la práctica, cuando flutter build linux comando flutter build linux , recibí este mensaje:



"No me dolió", pensé, estaba horrorizado por el peso de la carpeta de build , 287,5 MB, y debido a la simplicidad de mi alma, eliminé esta carpeta para siempre. Al final resultó que, en vano.

Después de eliminar el directorio de build , el proyecto dejó de comenzar. No pude restaurarlo, así que lo copié del ejemplo original. No sirvió de nada: el coleccionista maldijo los archivos faltantes.

Después de un poco de investigación, resultó que había un archivo snapshot_blob.bin.d en esta carpeta, en el que, aparentemente, se escribieron las rutas a todos los archivos utilizados en el proyecto. Agregué los caminos faltantes y funcionó.

Por lo tanto, en este momento Flutter no sabe cómo preparar versiones de lanzamiento para el escritorio. De todos modos, para Linux.

En general, si cierra los ojos a este signo negativo, la aplicación resultó como yo quería y se ve

entonces


Bono


Pasamos al bono prometido.

Incluso en la etapa de redacción de la aplicación, tenía el deseo de comprobar lo difícil que sería portarlo a otras plataformas. Comencemos con el teléfono celular.

Seguramente hay una manera menos bárbara, pero decidí que el camino más corto es directo. Por lo tanto, simplemente creé un nuevo proyecto de Flutter, transferí el archivo pubspec.yaml , los assets , las fonts y los directorios lib y le agregué la línea a AndroidManifest.xml :

 <uses-permission android:name="android.permission.INTERNET" /> 

La aplicación comenzó con media patada y obtuve esto

foto


Al principio, tuve que jugar con la web. No sabía cómo crear un proyecto web, así que utilicé las instrucciones de Internet, que por alguna razón no funcionaron. Ya quería escupir, pero encontré este manual.

Como resultado, todo resultó ser lo más simple posible: todo lo que se necesitaba era habilitar el soporte para aplicaciones web. Exprimir del manual:

 flutter channel master flutter upgrade flutter config --enable-web cd <into project directory> flutter create . flutter run -d chrome 

Luego transfirí los archivos necesarios a este proyecto de la misma manera bárbara y recibí tal

resultado


Impresiones generales


Al principio, trabajar con Flutter era inusual, intenté constantemente usar los enfoques habituales de React Native, y esto interfirió. Además, algo de redundancia del código de dardos era un poco molesto.

Después de tener un poco mi mano (y conos), comencé a ver las ventajas de Flutter sobre React Native. Voy a enumerar algunos.

Idioma . Dart es un lenguaje completamente comprensible y agradable con una fuerte escritura estática. Después de JavaScript, fue como un soplo de aire fresco. Dejé de tener miedo de que mi código se rompiera en tiempo de ejecución y fue una sensación agradable. Alguien puede decir que hay Flow y TypeScript, pero no es eso: en nuestros proyectos usamos ambos, y siempre se rompe algo en alguna parte. Cuando escribo en React Native, no puedo evitar sentir que mi código está en accesorios de fósforos que pueden romperse en cualquier momento. Con Flutter, olvidé este sentimiento, y si el precio es redundancia de código, entonces estoy listo para pagarlo.

Plataforma . En React Native, utiliza componentes nativos y esto generalmente es bueno. Pero debido a esto, a veces tiene que escribir código específico de la plataforma, así como detectar errores específicos de cada plataforma. Puede ser increíblemente agotador. Con Flutter, puede olvidar estos problemas como una pesadilla (aunque puede ser que en aplicaciones grandes las cosas no sean tan fáciles).

El medio ambiente Con el entorno en React Native, todo es triste. Los complementos de vscode se caen constantemente, el depurador puede engullir 16 gigas de operativo y 70 gigas de intercambio, colgando el sistema (por experiencia personal) y el escenario de corrección de errores más común: "eliminar node_modules, instalar paquetes nuevamente e intentar reiniciar varias veces". Esto generalmente ayuda, ¡pero bljad! No es así como debería ser, no es así.

Además, tendrá que ejecutar AndroidStudio y Xcode periódicamente, porque algunos se ponen de esta manera (para ser justos, con el lanzamiento de RN 0.60 esto mejoró).

En este contexto, el complemento oficial de Flutter para vscode se ve muy bien. Las sugerencias para el código le permiten familiarizarse con la plataforma sin consultar la documentación, el formateo automático resuelve el problema con el estilo de codificación, el depurador normal, etc.
En general, parece una herramienta más madura.

Multiplataforma . React Native profesa el principio "Aprende una vez, escribe en todas partes": una vez que aprendes, puedes escribir para diferentes plataformas. Es cierto que debajo de cada plataforma encontrarás problemas específicos. Pero tal vez esto sea solo una consecuencia de la inmadurez de React Native: en este momento, la última versión estable es 0.61. Quizás con el lanzamiento de la versión 1.0, la mayoría de estos problemas desaparecerán.

El enfoque Flutter es más parecido a Escribir una vez, compilar en todas partes. E incluso si el escritorio no está listo para la producción en este momento, la web también está en alfa, pero todo va a eso. Y la capacidad de tener una única base de código para todas las plataformas es un argumento fuerte.

Por supuesto, Flutter tampoco carece de defectos, pero un poco de experiencia en su uso no me permite identificarlos. Entonces, si desea una evaluación más objetiva, no dude en descontar el efecto de novedad.

En general, debe tenerse en cuenta que Flutter dejó sentimientos en su mayoría positivos, aunque tiene espacio para crecer. Y el próximo proyecto estaría más dispuesto a comenzar con él, y no con React Native.

El código fuente del proyecto se puede encontrar en GitHub .

PD: Aprovecho esta oportunidad para felicitar a todos los involucrados en el pasado Día del Maestro.

Source: https://habr.com/ru/post/470251/


All Articles