Utiliser Flutter de Google pour un développement mobile véritablement multiplateforme
Publié: 2022-03-10Flutter est un framework de développement mobile open source et multiplateforme de Google. Il permet de créer de belles applications hautes performances pour iOS et Android à partir d'une seule base de code. C'est également la plate-forme de développement du prochain système d'exploitation Fuchsia de Google. De plus, il est conçu de manière à pouvoir être transféré sur d'autres plates-formes, via des intégrateurs de moteur Flutter personnalisés.
Pourquoi Flutter a été créé et pourquoi vous devriez l'utiliser
Les boîtes à outils multiplateformes ont historiquement adopté l'une des deux approches suivantes :
- Ils encapsulent une vue Web dans une application native et créent l'application comme s'il s'agissait d'un site Web.
- Ils encapsulent des contrôles de plateforme natifs et fournissent une abstraction multiplateforme sur eux.
Flutter adopte une approche différente pour tenter d'améliorer le développement mobile. Il fournit un cadre de travail pour les développeurs d'applications et un moteur avec un environnement d'exécution portable pour héberger des applications. Le framework s'appuie sur la bibliothèque graphique Skia, fournissant des widgets qui sont réellement rendus, au lieu d'être simplement des wrappers sur des contrôles natifs.
Cette approche donne la flexibilité de créer une application multiplateforme de manière entièrement personnalisée, comme le propose l'option Web Wrapper, tout en offrant des performances fluides. Pendant ce temps, la riche bibliothèque de widgets fournie avec Flutter, ainsi qu'une multitude de widgets open source, en font une plate-forme très riche en fonctionnalités avec laquelle travailler. En termes simples, Flutter est la chose la plus proche que les développeurs mobiles aient eue pour le développement multiplateforme avec peu ou pas de compromis.
Dard
Les applications Flutter sont écrites en Dart, un langage de programmation développé à l'origine par Google. Dart est un langage orienté objet qui prend en charge à la fois la compilation anticipée et juste à temps, ce qui le rend bien adapté à la création d'applications natives, tout en offrant un flux de travail de développement efficace avec le rechargement à chaud de Flutter. Flutter a également récemment migré vers la version 2.0 de Dart.
Le langage Dart offre de nombreuses fonctionnalités présentes dans d'autres langages, notamment le ramasse-miettes, l'attente asynchrone, le typage fort, les génériques, ainsi qu'une riche bibliothèque standard.
Dart offre une intersection de fonctionnalités qui devraient être familières aux développeurs provenant de divers langages, tels que C#, JavaScript, F#, Swift et Java. De plus, Dart peut compiler en Javascript. Combiné avec Flutter, cela permet de partager du code sur des plateformes Web et mobiles.
Chronologie historique des événements
- avril 2015
Flutter (nom de code à l'origine Sky) présenté au Dart Developer Summit - novembre 2015
Sky renommé Flutter - Février 2018
Flutter beta 1 annoncé au Mobile World Congress 2018 - Avril 2018
Flutter beta 2 annoncé - Mai 2018
Flutter beta 3 annoncé à Google I/O. Google annonce que Flutter est prêt pour les applications de production
Comparaison avec d'autres plates-formes de développement
Apple/Android natif
Les applications natives offrent le moins de friction dans l'adoption de nouvelles fonctionnalités. Ils ont tendance à avoir des expériences utilisateur plus en phase avec la plate-forme donnée puisque les applications sont construites à l'aide des contrôles des fournisseurs de plates-formes eux-mêmes (Apple ou Google) et suivent souvent les directives de conception définies par ces fournisseurs. Dans la plupart des cas, les applications natives fonctionneront mieux que celles construites avec des offres multiplateformes, bien que la différence puisse être négligeable dans de nombreux cas en fonction de la technologie multiplateforme sous-jacente.
L'un des grands avantages des applications natives est qu'elles peuvent adopter immédiatement les toutes nouvelles technologies créées par Apple et Google en version bêta si vous le souhaitez, sans avoir à attendre une intégration tierce. Le principal inconvénient de la création d'applications natives est le manque de réutilisation du code sur toutes les plates-formes, ce qui peut rendre le développement coûteux si vous ciblez iOS et Android.
Réagir natif
React Native permet de créer des applications natives à l'aide de JavaScript. Les commandes réelles utilisées par l'application sont des commandes de plate-forme natives, de sorte que l'utilisateur final a la sensation d'une application native. Pour les applications qui nécessitent une personnalisation au-delà de ce que fournit l'abstraction de React Native, un développement natif peut toujours être nécessaire. Dans les cas où la quantité de personnalisation requise est importante, l'avantage de travailler dans la couche d'abstraction de React Native diminue au point où, dans certains cas, le développement de l'application de manière native serait plus bénéfique.
XamarinName
Lors de l'examen de Xamarin, il existe deux approches différentes qui doivent être évaluées. Pour leur approche la plus multiplateforme, il y a Xamarin.Forms. Bien que la technologie soit très différente de React Native, elle offre conceptuellement une approche similaire en ce sens qu'elle fait abstraction des contrôles natifs. De même, il présente des inconvénients similaires en ce qui concerne la personnalisation.
Deuxièmement, il y a ce que beaucoup appellent Xamarin-classique. Cette approche utilise les produits iOS et Android de Xamarin indépendamment pour créer des fonctionnalités spécifiques à la plate-forme, tout comme lors de l'utilisation directe d'Apple/Android natif, en utilisant uniquement C# ou F# dans le cas Xamarin. L'avantage avec Xamarin est que le code non spécifique à la plate-forme, des éléments tels que la mise en réseau, l'accès aux données, les services Web, etc. pourraient être partagés.
Contrairement à ces alternatives, Flutter tente de fournir aux développeurs une solution multiplateforme plus complète, avec une réutilisation du code, des interfaces utilisateur fluides et performantes et d'excellents outils.
Présentation d'une application Flutter
Créer une application
Après avoir installé Flutter, créer une application avec Flutter est aussi simple que d'ouvrir une ligne de commande et d'entrer flutter create [app_name]
, de sélectionner la commande "Flutter : Nouveau projet" dans VS Code ou de sélectionner "Démarrer un nouveau projet Flutter" dans Android. Studio ou IntelliJ.
Que vous choisissiez d'utiliser un IDE ou la ligne de commande avec votre éditeur préféré, le nouveau modèle d'application Flutter vous offre un bon point de départ pour une application.
L'application apporte le package flutter
/ material.dart
pour offrir un échafaudage de base pour l'application, comme une barre de titre, des icônes matérielles et des thèmes. Il configure également un widget avec état pour montrer comment mettre à jour l'interface utilisateur lorsque l'état de l'application change.
Options d'outillage
Flutter offre une flexibilité incroyable en matière d'outillage. Les applications peuvent tout aussi facilement être développées à partir de la ligne de commande avec n'importe quel éditeur, comme elles peuvent l'être à partir d'un IDE pris en charge comme VS Code, Android Studio ou IntelliJ. L'approche à adopter dépend en grande partie de la préférence du développeur.
Android Studio offre le plus de fonctionnalités, telles qu'un inspecteur Flutter pour analyser les widgets d'une application en cours d'exécution et surveiller les performances de l'application. Il propose également plusieurs refactorisations pratiques lors du développement d'une hiérarchie de widgets.
VS Code offre une expérience de développement plus légère dans la mesure où il a tendance à démarrer plus rapidement qu'Android Studio/IntelliJ. Chaque IDE propose des assistants d'édition intégrés, tels que la complétion de code, permettant l'exploration de diverses API ainsi qu'un bon support de débogage.
La ligne de commande est également bien prise en charge via la commande flutter
, ce qui facilite la création, la mise à jour et le lancement d'une application sans aucune autre dépendance d'outils au-delà d'un éditeur.
Rechargement à chaud
Indépendamment de l'outillage, Flutter maintient un excellent support pour le rechargement à chaud d'une application. Cela permet à une application en cours d'exécution d'être modifiée dans de nombreux cas, en maintenant son état, sans avoir à arrêter l'application, à la reconstruire et à la redéployer.
Le rechargement à chaud augmente considérablement l'efficacité du développement en permettant une itération plus rapide. Cela rend vraiment la plate-forme un plaisir de travailler avec.
Essai
Flutter inclut un utilitaire WidgetTester
pour interagir avec les widgets d'un test. Le nouveau modèle d'application inclut un exemple de test pour montrer comment l'utiliser lors de la création d'un test, comme indiqué ci-dessous :
// 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); }); }
Utiliser des packages et des plugins
Flutter ne fait que commencer, mais il existe déjà un riche écosystème de développeurs : une pléthore de packages et de plugins sont déjà disponibles pour les développeurs.
Pour ajouter un package ou un plug-in, incluez simplement la dépendance dans le fichier pubspec.yaml dans le répertoire racine de l'application. Ensuite, exécutez flutter packages get
depuis la ligne de commande ou via l'IDE, et les outils de Flutter apporteront toutes les dépendances requises.
Par exemple, pour utiliser le populaire plugin de sélection d'images pour Flutter, le pubspec.yaml n'a qu'à le lister comme une dépendance comme ceci :
dependencies: image_picker: "^0.4.1"
Ensuite, l'exécution de flutter packages get
apporte tout ce dont vous avez besoin pour l'utiliser, après quoi il peut être importé et utilisé dans Dart :
import 'package:image_picker/image_picker.dart';
Widget
Tout dans Flutter est un widget. Cela inclut des éléments d'interface utilisateur, tels que ListView
, TextBox
et Image
, ainsi que d'autres parties du framework, notamment la mise en page, l'animation, la reconnaissance des gestes et les thèmes, pour n'en nommer que quelques-uns.
En faisant en sorte que tout soit un widget, l'application entière, qui est également un widget, peut être représentée dans la hiérarchie des widgets. Avoir une architecture où tout est un widget indique clairement d'où proviennent certains attributs et comportements appliqués à une partie d'une application. Ceci est différent de la plupart des autres frameworks d'application, qui associent les propriétés et le comportement de manière incohérente, les attachant parfois à partir d'autres composants dans une hiérarchie et parfois sur le contrôle lui-même.
Exemple de widget d'interface utilisateur simple
Le point d'entrée d'une application Flutter est la fonction principale. Pour mettre un widget pour un élément de l'interface utilisateur à l'écran, appelez runApp()
main()
transmettez-lui le widget qui servira de racine à la hiérarchie des widgets.
import 'package:flutter/material.dart'; void main() { runApp( Container(color: Colors.lightBlue) ); }
Il en résulte un widget Container
bleu clair qui remplit l'écran :
Widgets sans état ou avec état
Les widgets existent en deux versions : sans état et avec état. Les widgets sans état ne changent pas leur contenu après avoir été créés et initialisés, tandis que les widgets avec état conservent un état qui peut changer pendant l'exécution de l'application, par exemple en réponse à l'interaction de l'utilisateur.
Dans cet exemple, un widget FlatButton
et un widget Text
sont dessinés à l'écran. Le widget Text
démarre avec une String
par défaut pour son état. Appuyer sur le bouton entraîne un changement d'état qui entraînera la mise à jour du widget Text
, affichant un nouveau String
.
Pour encapsuler un widget, créez une classe qui dérive de StatelessWidget
ou StatefulWidget
. Par exemple, le Container
bleu clair pourrait s'écrire comme suit :
class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Container(color: Colors.lightBlue); } }
Flutter appellera la méthode de construction du widget lorsqu'il sera inséré dans l'arborescence du widget afin que cette partie de l'interface utilisateur puisse être rendue.
Pour un widget avec état, dérivez de StatefulWidget
:
class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } }
class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } }
Les widgets avec état renvoient une classe State
responsable de la construction de l'arborescence des widgets pour un état donné. Lorsque l'état change, la partie associée de l'arborescence du widget est reconstruite.
Dans le code suivant, la classe State
met à jour une String
lorsqu'un bouton est cliqué :
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)), ], ) ) ) ); } }
L'état est mis à jour dans une fonction qui est passée à setState()
. Lorsque setState()
est appelée, cette fonction peut définir n'importe quel état interne, comme la chaîne dans cet exemple. Ensuite, la méthode de build
sera appelée, mettant à jour l'arborescence du widget avec état.
Notez également l'utilisation du widget Directionality
pour définir la direction du texte pour tous les widgets de sa sous-arborescence qui en ont besoin, tels que les widgets Text
. Les exemples ici construisent du code à partir de zéro, donc la Directionality
est nécessaire quelque part dans la hiérarchie des widgets. Cependant, l'utilisation du widget MaterialApp
, comme avec le modèle d'application par défaut, définit implicitement la direction du texte.
Mise en page
La fonction runApp
gonfle le widget pour remplir l'écran par défaut. Pour contrôler la disposition des widgets, Flutter propose une variété de widgets de disposition. Il existe des widgets pour effectuer des mises en page qui alignent les widgets enfants verticalement ou horizontalement, étendent les widgets pour remplir un espace particulier, limitent les widgets à une certaine zone, les centrent sur l'écran et permettent aux widgets de se chevaucher.
Deux widgets couramment utilisés sont Row
et Column
. Ces widgets effectuent des mises en page pour afficher leurs widgets enfants horizontalement (Row) ou verticalement (Column).
L'utilisation de ces widgets de mise en page implique simplement de les enrouler autour d'une liste de widgets enfants. Le mainAxisAlignment
contrôle la façon dont les widgets sont positionnés le long de l'axe de mise en page, centrés, au début, à la fin ou avec diverses options d'espacement.
Le code suivant montre comment aligner plusieurs widgets enfants dans une Row
ou une 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), ], ); } }
Répondre au toucher
L'interaction tactile est gérée avec des gestes, qui sont encapsulés dans la classe GestureDetector
. Puisqu'il s'agit également d'un widget, l'ajout de la reconnaissance des gestes est aussi simple que d'envelopper des widgets enfants dans un GestureDetector
.
Par exemple, pour ajouter la gestion tactile à un Icon
, faites-en un enfant d'un GestureDetector
et définissez les gestionnaires du détecteur pour les gestes que vous souhaitez capturer.
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), ); } }
Dans ce cas, lorsqu'un appui, un appui double ou un appui long est effectué sur l'icône, le texte associé est imprimé :
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
En plus des simples gestes de tapotement, il existe une multitude de reconnaissances, pour tout, du panoramique et de la mise à l'échelle au glissement. Ceux-ci facilitent la création d'applications interactives.
Peinture
Flutter propose également une variété de widgets avec lesquels peindre, notamment ceux qui modifient l'opacité, définissent des chemins de détourage et appliquent des décorations. Il prend même en charge la peinture personnalisée via le widget CustomPaint
et les classes CustomPainter
et Canvas
associées.
Un exemple de widget de peinture est le DecoratedBox
, qui peut peindre une BoxDecoration
à l'écran. L'exemple suivant montre comment l'utiliser pour remplir l'écran avec un remplissage dégradé :
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 ), ), ); } }
Animation
Flutter inclut une classe AnimationController
qui contrôle la lecture de l'animation dans le temps, y compris le démarrage et l'arrêt d'une animation, ainsi que la variation des valeurs d'une animation. De plus, il existe un widget AnimatedBuilder
qui permet de construire une animation en conjonction avec un AnimationController
.
Tout widget, tel que l'étoile décorée montrée précédemment, peut avoir ses propriétés animées. Par exemple, la refactorisation du code en un StatefulWidget
, puisque l'animation est un changement d'état, et le passage d'un AnimationController
à la classe State
permet d'utiliser la valeur animée lors de la construction du 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 ), ), ); } ); } }
Dans ce cas, la valeur est utilisée pour faire varier la taille du widget. La fonction builder
est appelée chaque fois que la valeur animée change, faisant varier la taille de l'étoile sur 750 ms, créant une échelle en effet :
Utilisation des fonctionnalités natives
Canaux de la plate-forme
Afin de fournir un accès aux API de plate-forme natives sur Android et iOS, une application Flutter peut utiliser des canaux de plate-forme. Ceux-ci permettent au code Flutter Dart d'envoyer des messages à l'application iOS ou Android hôte. De nombreux plugins open source disponibles sont créés à l'aide de la messagerie sur les canaux de la plate-forme. Pour apprendre à travailler avec les canaux de plate-forme, la documentation Flutter comprend un bon document qui montre comment accéder aux API de batterie natives.
Conclusion
Même en version bêta, Flutter offre une excellente solution pour créer des applications multiplateformes. Avec son excellent outillage et son rechargement à chaud, il apporte une expérience de développement très agréable. La richesse des packages open source et l'excellente documentation facilitent la prise en main. À l'avenir, les développeurs de Flutter pourront cibler Fuchsia en plus d'iOS et d'Android. Compte tenu de l'extensibilité de l'architecture du moteur, cela ne me surprendrait pas de voir Flutter atterrir également sur une variété d'autres plates-formes. Avec une communauté grandissante, c'est le moment idéal pour se lancer.
Prochaines étapes
- Installer Flutter
- Dart Language Tour
- Flutter Codelabs
- Cours Flutter Udacity
- Code source de l'article