Comment j'ai fait une application de bureau sur Flutter (+ bonus)

Récemment, j'ai appris que la prochaine version de Flutter (1.9) était sortie , ce qui promet divers avantages, y compris une prise en charge précoce des applications Web.

Au travail, je développe des applications mobiles sur React Native, mais je regarde Flutter avec curiosité. Pour ceux qui ne sont pas au courant: sur Flutter, vous pouvez déjà créer des applications pour Android et iOS, la prise en charge des applications Web est en cours de préparation pour la sortie, et il est également prévu de prendre en charge le bureau.

Tel est «un anneau pour tout gouverner».

Après avoir réfléchi quelques jours sur le type d'application que vous pouvez essayer de faire, j'ai décidé de choisir une tâche avec un astérisque - de quoi avons-nous besoin de ces pistes bien usées? Balancez-vous sur le bureau et surmontez héroïquement les difficultés! Pour l'avenir, je dirai que presque aucune difficulté n'est apparue.

Under the cut - une histoire sur la façon dont j'ai résolu les tâches habituelles du programmeur React Native en utilisant les outils Flutter, ainsi que l'impression générale de la technologie.



En réfléchissant aux fonctionnalités de Flutter que je voudrais «toucher», j'ai décidé que dans ma candidature, il faudrait:

  • demandes à l'API distante;
  • transitions entre écrans;
  • Animations de transition
  • gestionnaire d'état - redux ou quelque chose de similaire.

Je ne sais pas comment faire un backend, j'ai donc décidé de rechercher une API ouverte tierce. En conséquence, je me suis installé sur cette ressource - cours CBR en XML et JSON, API . Eh bien, ici, j'ai finalement décidé de la fonctionnalité de l'application: il y aura deux écrans, sur le principal il y a une liste de devises au taux CBR, lorsque vous cliquez sur un élément de la liste, nous ouvrons un écran avec des informations détaillées.

La préparation


Étant donné que la commande flutter create ne sait pas encore comment créer un projet pour Windows / Linux (seul Mac est actuellement pris en charge, utilisez l'indicateur --macos pour cela), vous devez utiliser ce référentiel, où il existe un exemple préparé. Nous clonons le référentiel, prenons l' example dossier à partir de là, si nécessaire, renommez-le et continuez à travailler dessus.

Étant donné que la prise en charge des plates-formes de bureau est toujours en cours de développement, vous devez toujours effectuer un certain nombre de manipulations. Pour accéder aux fonctionnalités en cours de développement, exécutez dans le terminal:

 flutter channel master flutter upgrade 

De plus, vous devez indiquer à Flutter qu'il peut utiliser votre plateforme:

 flutter config --enable-linux-desktop 

ou

 flutter config --enable-macos-desktop 

ou

 flutter config --enable-windows-desktop 

Si tout s'est bien passé, alors en exécutant la commande flutter doctor vous devriez voir une sortie similaire:



Donc, le décor est prêt, le public dans la salle - nous pouvons commencer.

Disposition


La première chose qui attire votre attention après React Native est l'absence d'un langage de balisage spécial à la JSX. Flutter vous oblige à écrire le balisage et la logique métier dans Dart . Au début, c'est ennuyeux: le look n'a rien à saisir, le code semble lourd, et même ces crochets sont à la fin du composant!

Par exemple, tels que:



Et ce n'est pas la limite! Cela vaut la peine d'en enlever un au mauvais endroit et un passe-temps agréable (non) vous est garanti.

De plus, en raison des particularités des composants de style dans Flutter, pour les gros composants, le retrait du bord gauche de l'éditeur augmente assez rapidement, et avec lui le nombre de crochets fermés.

Cette fonctionnalité est que dans les styles Flutter sont les mêmes composants (pour être plus précis - widgets).

Si dans React Native pour disposer trois boutons dans une rangée à l'intérieur de la View afin qu'ils répartissent uniformément l'espace du conteneur, il me suffit de spécifier flexDirection: 'row' pour la View dans les styles et d'ajouter flex: 1 pour les boutons dans les styles, alors Flutter a un composant distinct Row pour organiser les éléments dans une ligne et une autre pour "extensibilité" d'un élément sur tout l'espace disponible: Expanded .

En conséquence, au lieu 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> 

nous devons écrire comme ceci:

 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'), ), ), ], ), ) 

Plus verbeux, non?

Vous pouvez également ajouter un cadre aux bords arrondis à ce conteneur. Dans React Native, nous ajoutons simplement aux styles:

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

Dans Flutter, nous devons ajouter quelque chose comme ça aux arguments du conteneur:

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

En général, au début, mon balisage s'est transformé en d'énormes feuilles de code, dans lesquelles le diable se cassait la jambe. Cependant, tout n'est pas si mauvais.

Tout d'abord, les gros composants doivent bien sûr être décomposés - placés dans des widgets séparés ou au moins dans les méthodes de votre classe de widgets.

Deuxièmement, le plugin Flutter dans VS Code aide beaucoup - dans l'image ci-dessus, les commentaires sur les crochets sont signés par le plugin lui-même (et ils ne peuvent pas être supprimés), ce qui aide à ne pas se confondre entre crochets. De plus, des outils de formatage automatique - après une demi-heure, vous vous habituez à appuyer périodiquement sur Ctrl+Shift+I pour formater le code.

De plus, la syntaxe du langage Dart dans la deuxième édition est devenue beaucoup plus agréable, donc à la fin de la journée, j'ai déjà aimé l'utiliser. Insolite? Oui Mais pas désagréable.

Demandes d'API


Dans React Native, pour obtenir des données de certaines API, nous utilisons généralement la méthode fetch , qui nous renvoie Promise .

À Flutter, la situation est similaire. Après avoir regardé les exemples dans la documentation, j'ai ajouté le package http à pubspec.yaml (un analogue de package.json du monde JS) et j'ai écrit quelque chose comme ceci:

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

L'objet Future un sens très similaire à Promise, donc tout est assez transparent ici. Eh bien, pour sérialiser / désérialiser des objets json, vous pouvez utiliser le concept de classes de modèle avec des méthodes spéciales de fromJSON / toJSON . Vous pouvez en savoir plus à ce sujet dans la documentation .

Transition entre écrans


Malgré le fait que je faisais une application de bureau, du point de vue de Flutter, il n'y a aucune différence sur la plate-forme sur laquelle elle tourne. Eh bien, c'est dans mon cas, il en est ainsi, en général - je ne sais pas. En fait, la fenêtre système dans laquelle l'application Flutter est lancée est le même écran qu'un smartphone.

La transition entre les écrans est assez banale: nous créons une classe de widgets d'écran, puis utilisons la classe Navigator standard.

Dans le cas le plus simple, cela pourrait ressembler à ceci:

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

Si votre application comporte plusieurs écrans, il est plus raisonnable de préparer d'abord un dictionnaire de routes, puis d'utiliser la méthode pushNamed . Un petit exemple de la documentation:

 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'); }, ... 

De plus, vous pouvez préparer une animation spéciale pour basculer entre les écrans et écrire quelque chose comme ceci:

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

Ici ScaleRoute est une classe spéciale pour créer des animations de transition. De bons exemples de telles animations peuvent être trouvés ici .

Gestion de l'État


Il arrive que nous ayons besoin d'accéder à certaines données de n'importe quelle partie de notre application. Dans React Native, redux souvent utilisé (sinon le plus souvent) à ces fins.

Pour Flutter, il existe un référentiel qui montre des exemples d'utilisation de diverses architectures d'application - il y a Redux, MVC et MVU, et même ceux dont je n'ai jamais entendu parler auparavant.

Après avoir fouillé un peu dans ces exemples, j'ai décidé de m'arrêter sur Provider .

En général, l'idée est assez simple: nous créons une classe spéciale qui ChangeNotifier classe ChangeNotifier , dans laquelle nous allons stocker nos données, les mettre à jour en utilisant les méthodes de cette classe et les récupérer si nécessaire. Voir la documentation du package pour plus de détails.

Pour ce faire, ajoutez le package provider à pubspec.yaml et préparez la classe Provider. Dans mon cas, cela ressemble à ceci:

 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(); } } 

Ici, Rate est ma classe de modèle de devise (avec le name champs, le code , la value , etc.), currentrate est le champ dans lequel la devise sélectionnée sera stockée et setCurrentRate est la méthode par laquelle la valeur de currentrate est currentrate .

Pour attacher notre fournisseur à l'application, nous changeons le code de la classe d'application:

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

Voilà, maintenant si nous voulons enregistrer la devise sélectionnée, nous écrivons quelque chose comme ceci:

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

Et si nous voulons obtenir la valeur stockée, alors ceci:

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

Tout est assez transparent et sans passe-partout (contrairement à Redux). Bien sûr, peut-être que pour des applications plus complexes, tout se passera moins bien, mais pour ceux comme mon exemple, les vins purs.

Créer une application


En théorie, la commande flutter build <platform> est utilisée pour construire l'application. En pratique, lorsque j'ai exécuté la commande flutter build linux , j'ai reçu ce message:



"Cela n'a pas fait de mal", pensais-je, j'ai été horrifié par le poids du dossier de build - 287,5 Mo - et à cause de la simplicité de mon âme, j'ai supprimé ce dossier pour toujours. Il s'est avéré - en vain.

Après avoir supprimé le répertoire de build , le projet a cessé de démarrer. Je n'ai pas pu le restaurer, je l'ai donc copié à partir de l'exemple d'origine. Cela n'a pas aidé - le collectionneur a juré sur les fichiers manquants.

Après avoir effectué quelques recherches, il s'est avéré que dans ce dossier il y avait un fichier snapshot_blob.bin.d , qui, apparemment, contient les chemins d'accès à tous les fichiers utilisés dans le projet. J'ai ajouté les chemins manquants et cela a fonctionné.

Ainsi, pour le moment, Flutter ne sait pas comment préparer les versions de version pour le bureau. Quoi qu'il en soit, pour Linux.

En général, si vous fermez les yeux sur ce moins, l'application s'est avérée comme je le voulais et ressemble

donc


Bonus


Nous passons au bonus promis.

Même au stade de la rédaction de l'application, j'avais envie de vérifier à quel point il serait difficile de la porter sur d'autres plateformes. Commençons par le téléphone portable.

Il y a sûrement un moyen moins barbare, mais j'ai décidé que le chemin le plus court est direct. Par conséquent, j'ai simplement créé un nouveau projet Flutter, transféré le fichier pubspec.yaml , les assets , les fonts et les répertoires lib et y ajouté la ligne dans AndroidManifest.xml :

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

L'application a commencé avec un demi-coup de pied et j'ai obtenu ceci

image


Au début, j'ai dû bricoler avec le web. Je ne savais pas comment créer un projet Web, j'ai donc utilisé les instructions d'Internet, qui pour une raison quelconque ne fonctionnaient pas. Je voulais déjà cracher, mais je suis tombé sur ce manuel.

En conséquence, tout s'est avéré être aussi simple que possible - il a seulement fallu inclure la prise en charge des applications Web. Extrait du manuel:

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

Ensuite, j'ai transféré les fichiers nécessaires à ce projet de la même manière barbare et reçu ces

résultat


Impressions générales


Au début, travailler avec Flutter était inhabituel, j'ai constamment essayé d'utiliser les approches habituelles de React Native, et cela a interféré. De plus, une certaine redondance du code de fléchettes était un peu ennuyeuse.

Après avoir pris ma main (et mes cônes) un peu, j'ai commencé à voir les avantages de Flutter par rapport à React Native. J'en énumérerai quelques-uns.

Langue . Dart est un langage complètement compréhensible et agréable avec un typage statique fort. Après JavaScript, c'était comme une bouffée d'air frais. J'ai cessé d'avoir peur que mon code se casse pendant l'exécution et c'était une sensation agréable. Quelqu'un peut dire qu'il y a Flow et TypeScript, mais ce n'est pas cela - dans nos projets, nous avons utilisé les deux, et quelque chose d'autre a toujours éclaté quelque part. Lorsque j'écris dans React Native, je ne peux pas m'empêcher de penser que mon code est sur des accessoires d'allumettes qui peuvent se casser à tout moment. Avec Flutter, j'ai oublié ce sentiment, et si le prix est la redondance du code, alors je suis prêt à le payer.

Plateforme . Dans React Native, vous utilisez des composants natifs, ce qui est généralement bon. Mais à cause de cela, vous devez parfois écrire du code spécifique à la plate-forme, ainsi que des bugs spécifiques à chaque plate-forme. Cela peut être incroyablement fatigant. Avec Flutter, vous pouvez oublier ces problèmes comme un cauchemar (même s'il se peut que dans les grandes applications, les choses ne soient pas si fluides).

L'environnement . Avec l'environnement dans React Native, tout est triste. Les plugins vscode tombent constamment, le débogueur peut engloutir 16 gig de fonctionnement et 70 gig de swap, suspendant étroitement le système (par expérience personnelle), et le scénario de correction d'erreurs le plus courant: "supprimez node_modules, installez à nouveau les packages et essayez de redémarrer plusieurs fois." Cela aide généralement, mais bljad! Il ne devrait pas en être ainsi, non.

De plus, vous devrez exécuter AndroidStudio et Xcode périodiquement, car certains ne sont définis que de cette façon (en toute honnêteté, avec la sortie de RN 0.60, cela s'est amélioré).

Dans ce contexte, le plugin Flutter officiel pour vscode semble très bon. Les astuces pour le code vous permettent de vous familiariser avec la plate-forme sans consulter la documentation, le formatage automatique résout le problème avec le style de codage, le débogueur normal, etc.
En général, cela ressemble à un outil plus mature.

Multiplateforme . React Native professe le principe «Apprenez une fois, écrivez partout» - une fois que vous apprenez, vous pouvez écrire pour différentes plates-formes. Certes, sous chaque plate-forme, vous rencontrerez des problèmes qui lui sont spécifiques. Mais ce n'est peut-être qu'une conséquence de l'immaturité de React Native - pour le moment, la dernière version stable est la 0.61. Peut-être qu'avec la sortie de la version 1.0, la plupart de ces problèmes disparaîtront.

L'approche Flutter ressemble plus à Écrire une fois, compiler partout. Et même si le bureau n'est pas prêt pour la production pour le moment, le web est également en alpha, mais tout y est. Et la possibilité d'avoir une base de code unique pour toutes les plateformes est un argument fort.

Bien sûr, Flutter n'est pas non plus sans défauts, mais un peu d'expérience dans son utilisation ne me permet pas de les identifier. Donc, si vous voulez une évaluation plus objective, n'hésitez pas à réduire l'effet de nouveauté.

En général, il convient de noter que Flutter a laissé des sentiments principalement positifs, bien qu'il ait de la place pour grandir. Et le prochain projet, je serais plus disposé à commencer dessus, et non sur React Native.

Le code source du projet se trouve sur GitHub .

PS Je profite de cette occasion pour féliciter tous ceux qui ont participé à la dernière Journée des enseignants.

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


All Articles