Bonjour à tous et bienvenue dans ce nouveau tuto Flutter débutant sur la création d’une application mobile de musique.
Il s’agit d’un tuto Flutter débutant qui vous permettra de découvrir et de manipuler de nombreux concepts de Dart et Flutter.
Nous allons créer pas à pas le design de cette application avec Flutter et diviser notre code en deux fichiers.
Le premier fichier sera le point d’entrée qui comportera le code de la page d’accueil et le deuxième le code de notre page de lecture.
Nous allons donc séparer le tuto Flutter débutant en trois parties:
- Création et configuration de l’application
- Design de la page d’accueil de notre application
- Création de la page de lecture de musique
N’hésitez pas également à télécharger le code source de l’application pour la tester rapidement sur un émulateur.
Tuto Flutter Débutant | 1. Création et configuration de l'application
Dans cette partie nous allons créer un projet Flutter et configurer les éléments principaux de notre application, comme les ressources et les packages.
1.1 Création du projet avec la commande Flutter
Ici nous allons tout simplement utiliser la commande Flutter permettant de créer un nouveau projet et ensuite préparer notre application.
Nous nous positionnons dans notre dossier de développement et nous ouvrons ensuite le terminal pour lancer la commande:
flutter create musicplayer
Une fois que le projet est créé, nous pouvons l’ouvrir avec notre éditeur VS Code afin de le modifier.
On commence par se rendre dans le fichier principal main.dart et à supprimer tout son contenu, pour construire notre design en partant de zéro.
1.2 Importation de la photo de l'artiste
Notre application aura besoin de la photo de profil de l’artiste à mettre en avant, nous devons donc ajouter cette image dans le projet.
Pour cela, nous allons créer un dossier images dans lequel nous allons mettre la photo dont nous aurons besoin.
Le dossier image contiendra une seule image que nous utiliserons à plusieurs reprises dans notre application.
Ensuite nous nous rendons dans notre fichier pubspec.yaml afin de déclarer cette image en précisant son adresse comme ci-dessous:
assets: - images/ariana.jpeg
Nous entrons ensuite la commande suivante pour prendre en compte l’ajout de l'image dans notre application :
flutter pub get
1.3 Installation du package Google Fonts
À la première ligne de notre fichier main.dart, nous allons importer le package Material qui nous donnera accès à tous les widgets et autres outils que Flutter propose.
Ensuite, nous utiliser importer le package google_fonts qui donne accès à des centaines de polices: https://pub.dev/packages/google_fonts
Mais avant nous devons ajouter la dépendance du package google_fonts dans le fichier pubspec.yaml comme ceci:
dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 google_fonts: ^2.0.0
Une fois le package importé, enregistrez le fichier et le téléchargement devrait se faire automatiquement, sinon entrez la commande suivante:
flutter pub get
Notre fichier main.dart ressemble maintenant à ceci :
import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart';
Nos deux packages de base étant importés, nous allons commencer à construire notre application Flutter.
1.4 Déclaration de la fonction main() et de la classe MyApp
Tout d'abord, nous allons commencer par déclarer la fonction main() dans notre fichier main.dart.
C'est elle lancera le code de notre application, grâce à la fonction runApp() qui chargera la classe MyApp:
void main() { runApp(MyApp()); }
La classe MyApp va contenir la méthode build() qui permettra de dessiner l’interface utilisateur avec le widget MaterialApp() (application utilisant le material design).
Dans cette classe, nous utilisons le widget MaterialApp() qui est un StatelessWidget représentant la racine de notre application.
Nous donnons un titre à notre application avec le champ title, et nous mettons également l’attribut debugShowCheckedModeBanner à false pour supprimer la bannière de débogage.
Enfin nous avons le champ home qui va nous permettre de définir le Widget qui sera la page d'accueil de l'application.
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Music player', home: HomePage(), ); } }
Ici nous appelons le widget HomePage qui représentera notre page d'accueil que nous allons tout de suite créer.
Tuto Flutter Débutant | 2. Création de la page d’accueil
Dans cette deuxième partie, nous allons créer le design de notre page d’accueil en détaillant les différentes sections qui la composent.
2.1 Architecture de la HomePage
Afin de rendre notre code plus lisible, nous alors diviser notre page en plusieurs widgets qui seront créés indépendamment avant d’être appelé dans notre HomePage().
Pour commencer, nous déclarons notre classe Homepage comme un StatelessWidget qui va contenir la page d'accueil de notre application.
Cette classe va implémenter la méthode build qui va nous permettre de dessiner l’interface utilisateur de notre page d’accueil.
class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home'), ), body: Container(), ); } }
La méthode build retourne un Scaffold() qui est le widget qui implémente la structure de base d'une page en utilisant le material design.
Il nous permettra de définir notre appBar, notre body et ainsi qu'une bottomNavigationBar.
2.2 Création de la barre de navigation (appBar)
Notre appBar est assez simple, nous avons juste nos deux icônes à insérer en utilisant ici le widget IconButton.
Mais avant, nous allons permettre à l’image de notre artiste de s'afficher en dessous de notre AppBar.
Pour faire cela, nous allons étendre le body de notre application derrière la AppBar en précisant la propriété extendBodyBehindAppBar dans notre Scaffold avec pour valeur true:
Scaffold( extendBodyBehindAppBar: true,
Dans la déclaration de notre appBar, nous allons aussi renseigner deux propriétés à notre widget AppBar:
- la propriété elevation avec pour valeur zéro pour permettre d’annuler l’élévation par rapport à son parent
- la propriété backgroundColor avec pour valeur une couleur transparente afin de permettre que notre appBar ne cache pas l’image de l'artiste.
Pour le reste, cela reste classique, on place notre première icône dans le champ leading grâce au widget IconButton et pour la deuxième icône, on la définit dans les actions comme vous pouvez le voir dans le code ci-dessous:
class MyAppBar extends StatelessWidget implements PreferredSizeWidget { Size get preferredSize => new Size.fromHeight(60); @override Widget build(BuildContext context) { return AppBar( elevation: 0, leading: IconButton( icon: Icon( Icons.menu, color: Colors.white, size: 25, ), onPressed: null, ), actions: [ IconButton( icon: Icon( Icons.more_vert, color: Colors.white, size: 25, ), onPressed: null, ), ], backgroundColor: Colors.white.withOpacity(0), ); } }
Sans oublier de donner une taille et une couleur à nos icônes pour les rendre plus conforme au design originel.
Placez ensuite le widget de notre AppBar dans le champ dédié de notre Scaffold:
class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( extendBodyBehindAppBar: true, appBar: MyAppBar(), ); } }
2.3 Création de la HeaderSection de notre page d’accueil
Maintenant que notre appBar est prête, nous allons passer au contenu de notre page à construire qui sera subdivisé en deux sections.
La première section (HeaderSection) sera constituée de l’image de notre artiste, le nom de l’artiste et le bouton de lecture qui nous permettra de lancer la lecture des musiques.
La deuxième section (PlayListSection) quant à elle est constituée de la liste des musiques.
Ci-dessous, le code complet de notre HomePage qui fait appel aux sections que nous allons déclarer:
class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( extendBodyBehindAppBar: true, appBar: MyAppBar(), body: SingleChildScrollView( child: Column( children: [ HeaderSection(), PlayListSection(), ], ), ), bottomNavigationBar: BottomSection(), ); } }
Le corps de notre page d’accueil est contenu dans le widget principal SingleChildScrollView et dans une colonne pour permettre de scroller à la verticale.
Le premier enfant de notre HeaderSection est donc le widget Container qui lui va contenir l’image de notre artiste, le nom de l’artiste et le bouton de lecture.
Afin de structurer correctement les éléments de notre HeaderSection, nous utilisons ici le widget Stack dans lequel nous positionnons nos widgets enfants (Text et MaterialButton) en utilisant ici le widget Positioned.
class HeaderSection extends StatelessWidget { @override Widget build(BuildContext context) { return Container( height: 500, decoration: BoxDecoration( color: Colors.red, image: DecorationImage( image: AssetImage('images/ariana.jpeg'), fit: BoxFit.cover, ), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(50), ), ), child: Stack( children: [ Positioned( left: 20, bottom: 30, child: Text( 'Ariana Grande', style: GoogleFonts.arizonia( color: Colors.white, fontSize: 43, fontWeight: FontWeight.w800, ), ), ), Positioned( right: 0, bottom: 20, child: MaterialButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => PlayerPage(), ), ); }, color: Colors.blue, shape: CircleBorder(), child: Padding( padding: EdgeInsets.all(17), child: Icon( Icons.play_arrow_rounded, color: Colors.white, size: 30, ), ), ), ) ], ), ); } }
Afin que notre image recouvre entièrement l’arrière-plan de la première section, nous ajoutons la propriété fit à notre DecorationImage en lui donnant la valeur "BoxFit.Cover".
Pour permettre la navigation de la page d’accueil vers la page de Lecture qui sera créée dans la deuxième partie, nous ajoutons la propriété onPress à notre MaterialButton:
Navigator.push( context, MaterialPageRoute( builder: (context) => PlayerPage(), ), );
Vous devrez bien sûr déclarer votre page PlayerPage() pour voir faire fonctionner votre code et ainsi activer la navigation.
2.4 Création de la PlayListSection de notre page d’accueil
Comme deuxième enfant de la colonne de notre body, nous avons le widget PlayListSection que nous allons créer.
Ce dernier nous permet d’afficher une liste de lecture et de morceaux, dont voici le code final:
class PlayListSection extends StatelessWidget { final List playList = [ { 'title': 'No tears left to cry', 'played': false, 'duration': '3.16', }, { 'title': 'Imagine', 'played': true, 'duration': '3.14', }, { 'title': 'Into you', 'played': false, 'duration': '3.13', }, { 'title': '34 35', 'played': false, 'duration': '3.26', }, { 'title': 'positions', 'played': false, 'duration': '2.58', }, ]; @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.fromLTRB(30, 40, 20, 20), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Popular', style: TextStyle( color: Colors.black, fontSize: 25, fontWeight: FontWeight.w500, ), ), Container( margin: EdgeInsets.only(right: 10), child: Text( 'Show all', style: TextStyle( fontSize: 13, color: Colors.blue, ), ), ) ], ), SizedBox(height: 20), Column( children: playList.map((song) { return Container( height: 70, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( song['title'], style: TextStyle( color: song['played'] ? Colors.blue : Colors.black, fontSize: 16, fontWeight: FontWeight.w400, ), ), Row( children: [ Text( song['duration'], style: TextStyle( color: song['played'] ? Colors.blue : Colors.black, fontSize: 14, fontWeight: FontWeight.w400, ), ), SizedBox(width: 10), Icon( Icons.more_vert, color: song['played'] ? Colors.blue : Colors.grey, ) ], ) ], ), ); }).toList(), ) ], ), ); } }
Nous créons en premier lieu un variable locale pour stocker tous nos morceaux de musique ainsi que leurs informations:
final List playList = [ { 'title': 'No tears left to cry', 'played': false, 'duration': '3.16', }, { 'title': 'Imagine', 'played': true, 'duration': '3.14', }, [...] ];
Nous parcourons ensuite cette liste de données pour retourner à chaque fois le même widget mais avec des données différentes:
Column( children: playList.map((song) { return Text(song['title']); }).toList(), )
Vous pouvez utiliser cette méthode dès que vous avez des tests à effectuer en local dans votre application.
2.5 Création de notre bottomNavigationBar
Pour créer notre barre de navigation, nous utilisons le widget classique BottomNavigationBar() mais avec quelques propriétés pour l'adapter à notre design.
Nous souhaitons en effet avoir deux icônes ainsi qu'un texte à afficher dans notre BottomNavigationBar, ce qui est différent des versions classiques.
La principale différence consiste à masquer l'affichage du titre de chaque item de notre barre de navigation avec la propriété showSelectedLabels:
class BottomSection extends StatelessWidget { @override Widget build(BuildContext context) { return BottomNavigationBar( backgroundColor: Colors.blue, showSelectedLabels: false, showUnselectedLabels: false, items: [ BottomNavigationBarItem( icon: Icon( Icons.pause, color: Colors.white, ), label: '', ), BottomNavigationBarItem( icon: Text( "Imagine . Ariana Grande", style: TextStyle( color: Colors.white, fontSize: 13, fontWeight: FontWeight.w400, ), ), label: '', ), BottomNavigationBarItem( icon: Icon( Icons.arrow_circle_up, color: Colors.white, ), label: '', ), ], ); } }
Ensuite nous plaçons nos différents boutons et texte dans la BottomNavigationBarItem() pour qu'il s'affiche à la suite les uns des autres.
Voilà qui conclut le travail sur notre page d'accueil, qui sera plus détaillé que pour notre page de lecture.
Nous passerons en effet plus rapidement sur certaines parties qui ont déjà été abordées.
Tuto Flutter Débutant | 3. Création de la page de lecture
Dans cette partie, nous allons nous concentrer sur l’intégration du design de notre page de lecture, que nous divisons en 2 sections:
- La première section constituée de l’image de notre artiste et du nom de l’artiste.
- La deuxième section sera constituée du titre de la chanson + Nom du chanteur, la barre de progression de la lecture + temps de lecture et les boutons de contrôle du lecteur.
3.1 Création du fichier de la page lecture
Comme nous l’avons dit au début de ce tuto Flutter débutant, nous allons mettre le contenu de la page de lecture dans un fichier différent, ceci afin de bien organiser notre code et le rendre plus lisible et compréhensif.
Pour créer notre page de lecture, nous allons nous rendre dans le dossier lib et faire un clic droit, puis créer un nouveau fichier que nous allons appeler “player_page.dart”.
Cette page doit aussi être importée dans notre fichier main.dart afin de permettre la navigation entre les deux pages:
import 'player_page.dart';
Une fois notre page créé, nous importons les deux packages que nous utiliserons, notamment les packages material et Google Fonts comme le montre le code suivant :
import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; class PlayerPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( extendBodyBehindAppBar: true, appBar: MyAppBar(), body: Container(), ); } }
Une fois les packages importés, nous déclarons notre classe principale dans laquelle se trouvera tout notre code: la classe PlayerPage().
3.2 Création de la appBar
Notre appBar est très similaire à celle de la page d’accueil, elle est constituée de deux widgets de type icônes.
Le code de notre appBar est le suivant:
class MyAppBar extends StatelessWidget implements PreferredSizeWidget { Size get preferredSize => new Size.fromHeight(60); @override Widget build(BuildContext context) { return AppBar( elevation: 0, leading: IconButton( icon: Icon( Icons.chevron_left, color: Colors.white, size: 30, ), onPressed: () { Navigator.pop(context); }, ), actions: [ IconButton( icon: Icon( Icons.more_vert, color: Colors.white, size: 23, ), onPressed: null, ) ], backgroundColor: Colors.white.withOpacity(0), ); } }
2.3 Création du body de notre application
Maintenant, nous passons au corps de notre page, qui n'utilisera pas cette fois-ci de SingleChildScrollView().
Ici, nous plaçons directement le corps de notre page dans un Container, et ensuite dans une Column dans laquelle nous mettrons tous les widgets qui vont constituer notre corps.
Nous avons le code complet de notre body ci-dessous :
body: Container( color: Colors.white, child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ ImageAuthor(), PlayerControl(), ], ), ),
Notre body comme mentionné plus haut comporte deux sections principales et pour bien organiser cela, nous créons nos propres widgets que nous appelons par la suite comme des enfants de notre widget Column.
3.4 Création de la première section ImageAuthor
Nous déclarons donc le widget ImageAuthor qui est par la suite appelé comme vous pouvez le voir dans le code plus haut.
class ImageAuthor extends StatelessWidget { @override Widget build(BuildContext context) { return Container( height: 550, decoration: BoxDecoration( image: DecorationImage( image: AssetImage('images/ariana.jpeg'), fit: BoxFit.cover, ), ), child: Stack(children: [ TitleSection(), ArtistPictureSection(), ]), ); } }
Ici nous déclarons un Container de taille fixe et afin que notre image recouvre entièrement l’arrière-plan de la première section, nous ajoutons la propriété fit à notre DecorationImage en lui donnant la valeur "BoxFit.Cover".
Notre section avec l’image possède un widget Stack dans lequel nous mettons comme enfant un widget titleSection.
Nous positionnons ce titre avec le widget Positioned comme vous pouvez le voir plus haut. La déclaration de notre TitleSection est la suivante :
class TitleSection extends StatelessWidget { @override Widget build(BuildContext context) { return Container( height: 220, width: double.infinity, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Artist', style: GoogleFonts.lato( fontWeight: FontWeight.w300, fontSize: 14.0, color: Colors.white, ), ), Text( 'Ariana Grande', style: GoogleFonts.lato( fontWeight: FontWeight.w800, fontSize: 17.0, color: Colors.white, ), ), ], ), ); } }
Nous avons là un Container qui contient un widget Colum dans lequel nous mettons nos deux widgets enfants Text.
Bien évidemment, nous ajustons la couleur et la taille de nos textes que vous pouvez le voir dans le code.
Le deuxième widget enfant de notre première section est le widget ArtistPictureSection que nous avons déclaré plus bas.
Ce widget est constitué de deux éléments superposés : une image et un cadre, tous avec des bords du haut arrondis.
class ArtistPictureSection extends StatelessWidget { @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.only(top: 200), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topRight: Radius.circular(50), ), ), child: Stack( alignment: Alignment.center, children: [ Positioned( top: 65.0, child: Container( height: 250, width: 300, decoration: BoxDecoration( color: Colors.grey[350], borderRadius: BorderRadius.only( topRight: Radius.circular(50), ), ), ), ), Positioned( top: 50.0, child: Container( height: 275, width: 250, decoration: BoxDecoration( image: DecorationImage( image: AssetImage('images/ariana.jpeg'), fit: BoxFit.cover, colorFilter: ColorFilter.mode( Colors.blue.withOpacity(1), BlendMode.darken), ), borderRadius: BorderRadius.only( topRight: Radius.circular(50), ), ), ), ), ], ), ); } }
Nous définissions un Container avec une hauteur et largeur fixe puis nous utilisons le widget Stack à l’intérieur du quel nous avons nos deux widgets Positionned.
Ces derniers seront stylisés afin d’avoir le rendu que l’on veut. Notamment nous les positionnons et leur donnons les bordures arrondies.
Pour ce qui est de notre image superposée, nous l’appliquons un filtre grâce à la propriété colorFilter.
3.5 Création de la deuxième section playerControl
Nous passons à la dernière partie de ce tuto Flutter débutant en créant la dernière section qui elle est composé de quatre widgets principaux que nous déclarons à la suite.
class PlayerControl extends StatelessWidget { @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.only(top: 30), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ PlayingMusicTitle(), MusicSliderSection(), DurationSection(), MusicControlButtonSection() ], ), ); } }
Cette section est définie dans une Column centré ici grâce à la propriété mainAxisAlignment et prend comme valeur MainAxisAlignment.center.
En premier, nous avons le widget PlayingMusicTitle que nous appelons. Ce widget a été créé plus bas et contient le titre de la musique en cours de lecture ainsi que le nom de l’artiste.
Voilà le code de la section PlayingMusicTitle:
class PlayingMusicTitle extends StatelessWidget { @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.only(bottom: 10), child: Column( children: [ Text( 'Imagine', style: GoogleFonts.lato( fontWeight: FontWeight.w700, fontSize: 25.0, color: Colors.black, ), ), SizedBox(width: 3), Text( 'Ariana Grande', style: GoogleFonts.lato( fontWeight: FontWeight.w400, fontSize: 14.0, color: Colors.blue, ), ), ], ), ); } }
Nous utilisons ici deux widgets de type Text pour faire apparaître nos deux textes. Ensuite nous faisons un peu de mise en forme en changeant la police d’écriture, la couleur et la taille de police.
Le deuxième widget principal de notre dernière section est constitué de la barre de progression de lecture. Nous l’appelons MusicSliderSection et son code est le suivant:
class MusicSliderSection extends StatelessWidget { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(5), child: Slider( value: 19, min: 1.0, max: 100, divisions: 10, activeColor: Colors.blue, inactiveColor: Colors.grey, onChanged: (double newValue) {}, ), ); } }
Ici nous utilisons principalement le widget Slider pour créer notre barre de progression.
Le troisième widget de notre dernière section est constituée du temps de lecture de la musique. Nous le déclarons plus bas en le nommant DurationSection.
class DurationSection extends StatelessWidget { @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.only(left: 27, right: 27, bottom: 15), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '1.08', style: GoogleFonts.lato( fontWeight: FontWeight.w400, fontSize: 14.0, color: Colors.grey, ), ), Text( '3.14', style: GoogleFonts.lato( fontWeight: FontWeight.w400, fontSize: 14.0, color: Colors.grey, ), ), ], ), ); } }
Le widget DurationSection est constitué principalement de deux widgets Text. Nous appliquons ensuite différentes propriétés de style comme vous pouvez voir dans le code.
En fin nous terminons la deuxième section de notre page de lecture en appelant le dernier widget MusicControlButtonSection que nous allons déclarer par la suite.
Ce widget est principalement constitué de boutons de contrôle d’une musique en cours de lecture.
Le code complet de ce widget est le suivant :
class MusicControlButtonSection extends StatelessWidget { @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.only(top: 10), padding: EdgeInsets.all(5), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: Icon( Icons.shuffle, color: Colors.grey, size: 35, ), onPressed: null, ), IconButton( icon: Icon( Icons.skip_previous, color: Colors.black, size: 40, ), onPressed: null, ), ElevatedButton( onPressed: () {}, child: Container( padding: EdgeInsets.all(10), child: Icon( Icons.pause, color: Colors.black, size: 40.0, ), ), style: ElevatedButton.styleFrom( primary: Colors.white, shape: CircleBorder(), side: BorderSide( width: 1.0, color: Colors.grey, ), ), ), IconButton( icon: Icon( Icons.skip_next, color: Colors.black, size: 40, ), onPressed: null, ), IconButton( icon: Icon( Icons.repeat, color: Colors.grey, size: 35, ), onPressed: null, ), ], ), ); } }
Ce denier widget déclarer est principalement constitué de quatre widgets IconButton et d’un ElevatedButton.
Pour bien organiser cela, nous les placons tous dans un Row que nous alignons avec un espace identique entre chaque bouttons.
Nous utilisons le widget ElevatedButton afin d’avoir le bouton pause avec un contour circulaire.
En fin nous ajoutons des icônes à nos boutons et leur appliquons nos styles comme on peut le voir dans le code.
Conclusion
Voilà pour ce premier tutoriel Flutter débutant que je vous présente sur le blog.
C'est le premier d'une longue série qui viendra compléter la formation Flutter Révolution avec du contenu plus orienté sur le design pratique avec Flutter.
C'est aussi l'occasion pour un plus grand nombre de personnes de découvrir Flutter et de tester en direct son fonctionnement.
Si ce n'est pas déjà fait, commencez le cours gratuit Flutter pour l'installer et le configurer sur votre ordinateur.
Rejoignez le cours complet Flutter Révolution si vous souhaitez créer des applications complètes avec Firebase, Google Maps et Stripe.