Resolviendo problemas comunes multiplataforma cuando se trabaja con Flutter

Publicado: 2022-03-10
Resumen rápido ↬ Al usar marcos multiplataforma, las personas pueden olvidar los matices de cada una de las plataformas en las que quieren que se ejecute su código. Este artículo pretende abordar eso.

He visto mucha confusión en línea con respecto al desarrollo web con Flutter y, a menudo, lamentablemente es por las razones equivocadas.

Específicamente, las personas a veces lo confunden con los marcos multiplataforma móviles (y de escritorio) basados ​​​​en la web más antiguos, que básicamente eran solo páginas web que se ejecutaban dentro de los navegadores que se ejecutaban dentro de una aplicación de contenedor.

Eso fue realmente multiplataforma en el sentido de que las interfaces eran las mismas de todos modos porque solo tenía acceso a las interfaces normalmente accesibles en la Web.

Sin embargo, Flutter no es eso: se ejecuta de forma nativa en cada plataforma, y ​​significa que cada aplicación se ejecuta como si estuviera escrita en Java/Kotlin u Objective-C/Swift en Android e iOS, más o menos. Debe saberlo porque esto implica que debe cuidar las muchas diferencias entre estas plataformas tan diversas.

En este artículo, vamos a ver algunas de esas diferencias y cómo superarlas. Más específicamente, vamos a hablar sobre las diferencias de almacenamiento y UI, que son las que con mayor frecuencia causan confusión a los desarrolladores cuando escriben código de Flutter que quieren que sea multiplataforma.

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

Ejemplo 1: Almacenamiento

Recientemente escribí en mi blog sobre la necesidad de un enfoque diferente para almacenar JWT en aplicaciones web en comparación con las aplicaciones móviles.

Esto se debe a la naturaleza diferente de las opciones de almacenamiento de las plataformas y la necesidad de conocer cada una y sus herramientas de desarrollo nativas.

Web

Cuando escribe una aplicación web, las opciones de almacenamiento que tiene son:

  1. descargar/cargar archivos a/desde el disco, lo que requiere la interacción del usuario y, por lo tanto, solo es adecuado para archivos destinados a ser leídos o creados por el usuario;
  2. usar cookies, que pueden o no ser accesibles desde JS (dependiendo de si son o no httpOnly ) y se envían automáticamente junto con las solicitudes a un dominio determinado y se guardan cuando llegan como parte de una respuesta;
  3. usando JS localStorage y sessionStorage , accesible por cualquier JS en el sitio web, pero solo desde JS que es parte de las páginas de ese sitio web.

Móvil

La situación cuando se trata de aplicaciones móviles es completamente diferente. Las opciones de almacenamiento son las siguientes:

  1. documentos de aplicaciones locales o almacenamiento en caché, accesibles por esa aplicación;
  2. otras rutas de almacenamiento local para archivos legibles/creados por el usuario;
  3. NSUserDefaults y SharedPreferences respectivamente en iOS y Android para el almacenamiento de clave-valor;
  4. Keychain en iOS y KeyStore en Android para el almacenamiento seguro de cualquier dato y clave criptográfica, respectivamente.

Si no sabe eso, sus implementaciones serán un desastre porque necesita saber qué solución de almacenamiento está utilizando realmente y cuáles son las ventajas y desventajas.

Soluciones multiplataforma: un enfoque inicial

El uso del paquete de shared_preferences de Flutter utiliza localStorage en la Web, SharedPreferences en Android y NSUserDefaults en iOS. Esos tienen implicaciones completamente diferentes para su aplicación, especialmente si está almacenando información confidencial como tokens de sesión: el cliente puede leer localStorage , por lo que es un problema si es vulnerable a XSS. Aunque las aplicaciones móviles no son realmente vulnerables a XSS, SharedPreferences y NSUserDefaults no son métodos de almacenamiento seguro porque pueden verse comprometidos en el lado del cliente, ya que no son un almacenamiento seguro ni están encriptados. Esto se debe a que están destinados a las preferencias del usuario, como se menciona aquí en el caso de iOS y aquí en la documentación de Android cuando se habla de la biblioteca de seguridad que está diseñada para proporcionar contenedores a SharedPreferences específicamente para cifrar los datos antes de almacenarlos.

Almacenamiento seguro en dispositivos móviles

Las únicas soluciones de almacenamiento seguro en dispositivos móviles son Keychain y KeyStore en iOS y Android respectivamente, mientras que no hay almacenamiento seguro en la Web .

Sin embargo, el Keychain y el almacén de KeyStore son de naturaleza muy diferente: el Keychain es una solución genérica de almacenamiento de credenciales, mientras que el KeyStore se utiliza para almacenar (y puede generar) claves criptográficas , ya sean claves simétricas o claves públicas/privadas.

Esto significa que si, por ejemplo, necesita almacenar un token de sesión, en iOS puede dejar que el sistema operativo administre la parte de cifrado y simplemente envíe su token al Keychain , mientras que en Android es una experiencia un poco más manual porque necesita para generar (no codificar, eso es malo) una clave, utilícela para encriptar el token, almacene el token encriptado en SharedPreferences y almacene la clave en KeyStore .

Hay diferentes enfoques para eso, como lo son la mayoría de las cosas en seguridad, pero el más simple es probablemente usar el cifrado simétrico, ya que no hay necesidad de criptografía de clave pública ya que su aplicación cifra y descifra el token.

Obviamente, no necesita escribir un código específico de la plataforma móvil que haga todo eso, ya que hay un complemento de Flutter que hace todo eso, por ejemplo.

La falta de almacenamiento seguro en la web

Esa fue, en realidad, la razón que me obligó a escribir este post. Escribí sobre el uso de ese paquete para almacenar JWT en aplicaciones móviles y la gente quería la versión web de eso pero, como dije, no hay almacenamiento seguro en la web . no existe

¿Eso significa que su JWT tiene que estar a la vista?

No, en absoluto. Puedes usar cookies httpOnly , ¿no? JS no puede acceder a ellos y solo se envían a su servidor. El problema es que siempre se envían a su servidor, incluso si uno de sus usuarios hace clic en una URL de solicitud GET en el sitio web de otra persona y esa solicitud GET tiene efectos secundarios que no le agradarán a usted o a su usuario. Esto también funciona para otros tipos de solicitudes, solo que es más complicado. Se llama falsificación de solicitud entre sitios y no quieres eso. Es una de las amenazas de seguridad web mencionadas en los documentos de MDN de Mozilla, donde puede encontrar una explicación más completa.

Hay métodos de prevención. El más común es tener dos tokens, en realidad: uno de ellos llega al cliente como una cookie httpOnly , el otro como parte de la respuesta. Este último tiene que almacenarse en localStorage y no en cookies porque no queremos que se envíe automáticamente al servidor.

Resolviendo Ambos

¿Qué sucede si tiene una aplicación móvil y una aplicación web?

Eso se puede tratar de una de dos maneras:

  1. Use el mismo punto final de back-end, pero obtenga y envíe manualmente las cookies usando los encabezados HTTP relacionados con las cookies;
  2. Cree un punto final de back-end independiente que no sea web que genere un token diferente de cualquiera de los tokens utilizados por la aplicación web y luego permita la autorización JWT regular si el cliente puede proporcionar el token solo para dispositivos móviles.

Ejecutar código diferente en diferentes plataformas

Ahora, veamos cómo podemos ejecutar diferentes códigos en diferentes plataformas para poder compensar las diferencias.

Crear un complemento de Flutter

Especialmente para resolver el problema del almacenamiento, una forma de hacerlo es con un paquete de complementos: los complementos proporcionan una interfaz Dart común y pueden ejecutar diferentes códigos en diferentes plataformas, incluido el código Kotlin/Java o Swift/Objective-C nativo específico de la plataforma. . El desarrollo de paquetes y complementos es bastante complejo, pero se explica en muchos lugares de la Web y en otros lugares (por ejemplo, en los libros de Flutter), incluida la documentación oficial de Flutter.

Para plataformas móviles, por ejemplo, ya existe un complemento de almacenamiento seguro, y es flutter_secure_storage , para el cual puede encontrar un ejemplo de uso aquí, pero eso no funciona en la Web, por ejemplo.

Por otro lado, para el almacenamiento simple de clave-valor que también funciona en la web, hay un paquete de complementos desarrollado por Google multiplataforma llamado shared_preferences , que tiene un componente específico de la web llamado shared_preferences_web que usa NSUserDefaults , SharedPreferences o localStorage dependiendo de la plataforma.

TargetPlatform en Flutter

Después de importar package:flutter/foundation.dart , puedes comparar Theme.of(context).platform con los valores:

  • TargetPlatform.android
  • TargetPlatform.iOS
  • TargetPlatform.linux
  • TargetPlatform.windows
  • TargetPlatform.macOS
  • TargetPlatform.fuchsia

y escribe tus funciones para que, para cada plataforma que quieras soportar, hagan lo apropiado. Esto será especialmente útil para el siguiente ejemplo de diferencia de plataforma, y ​​son las diferencias en cómo se muestran los widgets en diferentes plataformas.

Para ese caso de uso, en particular, también hay un complemento flutter_platform_widgets razonablemente popular, que simplifica el desarrollo de widgets compatibles con la plataforma.

Ejemplo 2: diferencias en cómo se muestra el mismo widget

No puede simplemente escribir código multiplataforma y fingir que un navegador, un teléfono, una computadora y un reloj inteligente son lo mismo, a menos que desee que su aplicación de Android e iOS sea WebView y su aplicación de escritorio se cree con Electron. . Hay muchas razones para no hacer eso, y el objetivo de este artículo no es convencerlo de usar marcos como Flutter en su lugar, que mantienen su aplicación nativa, con todas las ventajas de rendimiento y experiencia del usuario que conlleva, mientras le permite escribir código que será el mismo para todas las plataformas la mayor parte del tiempo.

Sin embargo, eso requiere cuidado y atención, y al menos un conocimiento básico de las plataformas que desea admitir, sus API nativas reales y todo eso. Los usuarios de React Native deben prestar aún más atención a eso porque ese marco utiliza los widgets integrados del sistema operativo, por lo que en realidad debe prestar aún más atención a cómo se ve la aplicación probándola exhaustivamente en ambas plataformas, sin poder cambiar entre Widget de iOS y Material sobre la marcha como es posible con Flutter.

Qué cambia sin su solicitud

Hay algunos aspectos de la interfaz de usuario de su aplicación que se cambian automáticamente cuando cambia de plataforma. Esta sección también menciona qué cambios entre Flutter y React Native a este respecto.

Entre Android e iOS (Flutter)

Flutter es capaz de renderizar widgets de Material en iOS (y widgets de Cupertino (similares a iOS) en Android), pero lo que NO hace es mostrar exactamente lo mismo en Android e iOS: la tematización de Material se adapta especialmente a las convenciones de cada plataforma. .

Por ejemplo, las animaciones y transiciones de navegación y las fuentes predeterminadas son diferentes, pero no afectan tanto a su aplicación.

Lo que puede afectar algunas de sus elecciones cuando se trata de estética o UX es el hecho de que algunos elementos estáticos también cambian. Específicamente, algunos íconos cambian entre las dos plataformas, los títulos de la barra de aplicaciones están en el medio en iOS y a la izquierda en Android (a la izquierda del espacio disponible en caso de que haya un botón Atrás o el botón para abrir un Cajón (explicado aquí) en las pautas de Material Design y también conocido como menú de hamburguesas). Así es como se ve una aplicación de Material con un cajón en Android:

imagen de una aplicación de Android que muestra dónde aparece el título de la barra de la aplicación en las aplicaciones Flutter Android Material
Aplicación Material ejecutándose en Android: el título de la barra de aplicaciones se encuentra en el lado izquierdo del espacio disponible. (Vista previa grande)

Y cómo se ve la misma aplicación Material, muy simple, en iOS:

imagen de una aplicación de iOS que muestra dónde aparece el título de la barra de la aplicación en las aplicaciones Flutter iOS Material
Aplicación Material ejecutándose en iOS: el título de AppBar está en el medio. (Vista previa grande)

Entre móvil y web y con muescas de pantalla (Flutter)

En la Web hay una situación un poco diferente, como se menciona también en este artículo de Smashing sobre el desarrollo web receptivo con Flutter: en particular, además de tener que optimizar para pantallas más grandes y tener en cuenta la forma en que las personas esperan navegar por su sitio — que es el enfoque principal de ese artículo — debe preocuparse por el hecho de que a veces los widgets se colocan fuera de la ventana del navegador. Además, algunos teléfonos tienen muescas en la parte superior de la pantalla u otros impedimentos para la correcta visualización de tu aplicación debido a algún tipo de obstrucción.

Ambos problemas se pueden evitar envolviendo sus widgets en un widget SafeArea , que es un tipo particular de widget de relleno que asegura que sus widgets caigan en un lugar donde realmente se puedan mostrar sin que nada impida la capacidad de los usuarios para verlos. ya sea una restricción de hardware o software.

En reaccionar nativo

React Native requiere mucha más atención y un conocimiento mucho más profundo de cada plataforma, además de requerir que ejecutes el simulador de iOS y el emulador de Android como mínimo para poder probar tu aplicación en ambas plataformas: no es lo mismo y convierte sus elementos de la interfaz de usuario de JavaScript en widgets específicos de la plataforma. En otras palabras, sus aplicaciones React Native siempre se verán como iOS, con elementos de la interfaz de usuario de Cupertino, como a veces se les llama, y ​​sus aplicaciones de Android siempre se verán como las aplicaciones regulares de Material Design para Android porque usan los widgets de la plataforma.

La diferencia aquí es que Flutter renderiza sus widgets con su propio motor de renderizado de bajo nivel, lo que significa que puede probar ambas versiones de la aplicación en una plataforma.

Cómo sortear ese problema

A menos que busque algo muy específico, se supone que su aplicación se verá diferente en diferentes plataformas; de lo contrario, algunos de sus usuarios no estarán contentos.

Al igual que no debería simplemente enviar una aplicación móvil a la web (como escribí en la publicación Smashing mencionada anteriormente), no debería enviar una aplicación llena de widgets de Cupertino a los usuarios de Android, por ejemplo, porque será confuso para la mayor parte Por otro lado, tener la oportunidad de ejecutar una aplicación que tiene widgets destinados a otra plataforma le permite probar la aplicación y mostrársela a las personas en ambas versiones sin tener que usar necesariamente dos dispositivos para eso.

El otro lado: usar los widgets incorrectos por las razones correctas

Pero eso también significa que puede hacer la mayor parte de su desarrollo de Flutter en una estación de trabajo Linux o Windows sin sacrificar la experiencia de sus usuarios de iOS, y luego simplemente crear la aplicación para la otra plataforma y no tener que preocuparse por probarla a fondo.

Próximos pasos

Los marcos multiplataforma son increíbles, pero transfieren la responsabilidad a usted, el desarrollador, para comprender cómo funciona cada plataforma y cómo asegurarse de que su aplicación se adapte y sea agradable de usar para sus usuarios. Otras pequeñas cosas a considerar pueden ser, por ejemplo, usar diferentes descripciones para lo que podría ser en esencia lo mismo si hay diferentes convenciones en diferentes plataformas.

Es genial no tener que compilar las dos (o más) aplicaciones por separado usando diferentes idiomas, pero aún debe tener en cuenta que, en esencia, está compilando más de una aplicación y eso requiere pensar en cada una de las aplicaciones que está compilando. .

Más recursos

  • El sitio web de Flutter Gallery y la aplicación de Android, que muestran el uso de widgets de Flutter típicos de diferentes plataformas y su agnosticismo de plataforma.
  • Documentación de la API de Flutter en TargetPlatform
  • Documentación de Flutter sobre la creación de paquetes y complementos.
  • Flutter documentación sobre adaptaciones de la plataforma
  • Documentación de MDN sobre cookies