Cours Flutter
Cours Flutter
2024-2025
PLAN
Introduction à Flutter
Exemples de widgets
Les routes
4
INTRODUCTION À FLUTTER
Qu'est-ce que Dart ?
5
Technologi es
Applications natives
Android iOS
▸ ▸ Objective-C
TECHNOLOGIES
Java
▸ Kotlin (2017) ▸ Swift (2014)
Avantages Inconvénients
7
Applications hybrides
▸ Cordova, Ionic, …
▸
TECHNOLOGIES
Avantages Inconvénients
▸ Code partagé / réutilisation ▸ Performances
▸ ( Interface uti l i sateur
▸ Sécurité
spéci fique à chaque
plateforme)
8
Applications cross-plateformes
• Exemples de framework : React Native, Xamarin, Flutter
• Objectif : générer 1 application Android et iOS à partir du même code
TECHNOLOGIES
Avantages Inconvénients
• Code partagé / réutilisation • Peut nécessiter d’écrire du code
• Compétences similaires au web spécifique
9
Quelques
chiffres
QUELQUES CHIFFRES
Google Trends
[Link]
11
QUELQUES CHIFFRES
Stack Overflow Trends
Source :[Link]
12
INTRODUCTION À FLUTTER
Installation de l’environnement
[Link]
13
Les bases de
Dart pour
Flutter
LES BASES DE DART POUR FLUTTER
Conventions de nommage
15
LES BASES DE DART POUR FLUTTER
Les types en Dart
• Tout ce qui est placé dans une variable est un objet, y compris les types "int", "double",
"bool" et "null"
int a = 5;
16
LES BASES DE DART POUR FLUTTER
Les lists et les maps
• List : • Map :
Pas de type "Array" dans Dart
17
LES BASES DE DART POUR FLUTTER
Les structures de contrôle
if (age < 14)
{ print('Enfants');
} else if (age< 24)
{ print('Adolescents');
} else {
print('Adultes');
}
18
LES BASES DE DART POUR FLUTTER
Les boucles
List<String> students = ['s1', 's2', 's3'];
for (var student in students) {
print(student);
}
[Link]((student) {
print(student);
});
[Link]().forEach((index, student)
{
print('$index: $student');
});
19
LES BASES DE DART POUR FLUTTER
Les fonctions
int somme(int a, int b) {
return a + b;
}
20
LES BASES DE DART POUR FLUTTER
Les classes
class Person {
String name;
Person([Link]);
}
Person(String name) {
[Link] = name;
}
Person p = Person("toto");
21
Introduction
aux widget s
INTRODUCTION AUX WIDGETS
Qu'est-ce qu'un widget ?
23
INTRODUCTION AUX WIDGETS
Les widgets Stateless
24
INTRODUCTION AUX WIDGETS
Les widgets Stateless
25
INTRODUCTION AUX WIDGETS
Les widgets Stateful
• Comporte un état
• La méthode "build()" est déportée dans le state
• Une seule méthode : "State<T> createState()" où
<T> correspond à la classe StatefulWidget
• Méthode "build" appelée à chaque chaque
modification du state
• State = ensemble des attributs de la classe State
• "initState()" : méthode pour initialisation le state
26
INTRODUCTION AUX WIDGETS
Les widgets Stateful
• Comporte un état
• La méthode "build()" est déportée dans le state
• Une seule méthode : "State<T> createState()" où
<T> correspond à la classe StatefulWidget
• Méthode "build" appelée à chaque modification du
state
• State = ensemble des attributs de la classe State
• "initState()" : méthode pour initialisation le state
27
INTRODUCTION AUX WIDGETS
Les widgets Stateful
class MyApp extends StatefulWidget {
@override
_MyAppState createState() =>
_MyAppState();
}
@override
void initState() {
_counter = 0;
[Link]();
}
@override
Widget build(BuildContext context) {
return Text("Compteur $_counter");
}
}
28
INTRODUCTION AUX WIDGETS
Le widget Text
Text(
"Compteur $_counter",
style: TextStyle(
fontWeight: [Link],
fontSize: 16,
color: [Link],
),
);
29
INTRODUCTION AUX WIDGETS
Le widget Image
[Link]
assets:
- assets/image/
Center(
child: [Link]("assets/image/
my_image.jpg"),
)
[Link]('[Link]
30
INTRODUCTION AUX WIDGETS
Le widget Scaffold
Scaffold(
appBar: AppBar(
title: Text("My app"),
),
body: Center(
child: Text("Home page"),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
print("clicked");
},
child: Icon([Link]),
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
label: "home",
icon: Icon([Link])
),
BottomNavigationBarItem(
label: "calendar",
icon: Icon(Icons.calendar_today)
),
BottomNavigationBarItem(
label: "contact",
icon: Icon(Icons.contact_mail)
),
],
),
) 31
Les widgets
de layout
LES WIDGETS DE LAYOUT
Le widget container
Center(
child: Container(
height: 100,
width: 200,
color: [Link],
margin: [Link](20),
child: Center(
child: Text("Home page")
),
),
)
33
LES WIDGETS DE LAYOUT
Le widget padding
Center(
child: Container(
height: 100,
width: 200,
color: [Link],
margin: [Link](20),
child: Padding(
padding: const [Link](8.0),
child: Container(
color: [Link],
child: Center(
child: Text("Home page")
),
),
),
),
)
34
LES WIDGETS DE LAYOUT
Le widget column
SingleChildScrollView(
child: Column(
Container( height:
children: [
100,
Container(
color: [Link][400],
height: 100,
),
color: [Link][900],
Container( height:
),
100,
Container( height:
100, color: [Link][300],
),
color: [Link][800],
Container( height:
),
100,
Container( height:
100, color: [Link][200],
),
color: [Link][700],
Container( height:
),
100,
Container( height:
color: [Link][100],
100,
),
color: [Link][600],
),
],
Container( height:
),
100,
),
color: [Link][500],
),
35
LES WIDGETS DE LAYOUT
Le widget column
36
-
),
Le widget row
Container( width:
100,
height: 100,
color: [Link][800],
),
LES WIDGETS DE LAYOUT
Container( width:
100,
height: 100,
color: [Link][700],
),
Container( width:
100,
height: 100,
color: [Link][600],
),
Container( width:
100,
height: 100,
color: [Link][500],
),
Container( width:
100,
height: 100,
color: [Link][400],
),
Container( width:
100,
height: 100,
color: [Link][300],
),
Container( 37
width: 100,
LES WIDGETS DE LAYOUT
Le widget row
LES WIDGETS DE LAYOUT
Le widget ListTile
ListTile(
leading: FlutterLogo(size: 56.0),
title: Text('Two-line ListTile'),
subtitle: Text('Here is a second
line'),
trailing: Icon(Icons.more_vert),
)
39
Les widgets
relatif s aux
l istes
Le widget ListView
LES WIDGETS RELATIFS AUX LISTES
ListView(
children: [
Container(
width: 100,
height: 100,
color: [Link][900],
),
Container(
width: 100,
height: 100,
color: [Link][800],
),
Container(
width: 100,
height: 100,
color: [Link][700],
),
Container(
width: 100,
height: 100,
color: [Link][600],
),
],
)
41
Le widget ListTile
LES WIDGETS RELATIFS AUX LISTES
ListView(
children: [
ListTile(
leading: Icon(Icons.supervised_user_circle),
title: Text("my name"),
subtitle: Text("my job"),
trailing: Icon([Link]),
),
ListTile(
leading: Icon(Icons.supervised_user_circle),
title: Text("my name"),
subtitle: Text("my job"),
trailing: Icon([Link]),
),
ListTile(
leading: Icon(Icons.supervised_user_circle),
title: Text("my name"),
subtitle: Text("my job"),
trailing: Icon([Link]),
),
],
)
42
Les widgets
d’ Informations
LES WIDGETS D’INFORMATIONS
Card
Card(
child: Column(
mainAxisSize: [Link],
children: <Widget>[
const ListTile(
leading: Icon([Link]),
title: Text('The Enchanted Nightingale'),
subtitle: Text('Music by Julie Gable. Lyrics by
Sidney Stein.'),
),
Row(
mainAxisAlignment: [Link],
children: <Widget>[
TextButton(
child: const Text('BUY TICKETS'),
onPressed: () {/* ... */},
),
const SizedBox(width: 8),
TextButton(
child: const Text('LISTEN'),
onPressed: () {/* ... */},
),
const SizedBox(width: 8),
],
),
],
),
) 44
LES WIDGETS D’INFORMATIONS
ProgressIndicator
Column(
mainAxisAlignment:
[Link],
children: [
CircularProgressIndicator(),
SizedBox(height: 50,),
LinearProgressIndicator()
],
)
45
LES WIDGETS D’INFORMATIONS
Tooltip
Tooltip(
message: 'I am a Tooltip',
decoration: BoxDecoration(
borderRadius: [Link](25),
gradient:
LinearGradient(
colors: <Color>[
[Link].shade900,
[Link][400]!
]
),
),
height: 50,
padding: const [Link](8.0),
preferBelow: true,
textStyle: const TextStyle(
fontSize: 24,
color: [Link]
),
showDuration: Duration(seconds: 3),
child: Text('Hover over the text to show a
tooltip.'),
)
46
LES WIDGETS D’INFORMATIONS
AlertDialog
Future<void> _showMyDialog() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text('AlertDialog Title'),
content: SingleChildScrollView(
child: ListBody(
children: const <Widget>[
Text('This is a demo alert dialog.'),
Text('Would you like to approve of this
message?'),
],
),
),
actions: <Widget>[
TextButton(
child: const Text('Approve'),
onPressed: () {
[Link](context).pop();
},
),
],
);
},
);
} 47
Les widgets
d’ Input et de
sélectio n
TextField
LES WIDGETS D’INPUT ET DE SÉLECTION
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius:
[Link]([Link](10.0)),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: [Link]),
borderRadius:
[Link]([Link](10.0)),
),
hintText: "your text",
label: Text("label"),
fillColor: Colors.white70
),
)
border:
UnderlineInputBorder(),
49
2023 - 2024
TextField
LES WIDGETS D’INPUT ET DE SÉLECTION
TextField(
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
),
),
TextField(
controller: controller,
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
),
),
50
Form
LES WIDGETS D’INPUT ET DE SÉLECTION
final _formKey =
GlobalKey<FormState>();
Form(
key: _formKey,
child: Column(
children: [],
)
)
TextFormField(
ElevatedButton(
decoration: const InputDecoration(
onPressed: () {
border: OutlineInputBorder(),
if (_formKey.currentState!.validate()) {
labelText: 'Email',
[Link](context).showSnackBar(
hintText: 'Enter Email' const SnackBar(content: Text('Processing
),
Data')),
autovalidateMode:
);
}
[Link],
},
validator: (value) {
child: const Text('Submit'),
if (value!.isEmpty) { )
return "* Required";
} else if (![Link]())
{
return "Check your email";
} else
return null;
}, 51
)
Form
LES WIDGETS D’INPUT ET DE SÉLECTION
Form( if (value!.isEmpty) {
key: _formKey, return "* Required";
child: Column( } else if ([Link] < 6) {
children: <Widget>[ return "Password should be atleast 6
TextFormField( characters";
decoration: const InputDecoration( } else if ([Link] > 15) {
border: OutlineInputBorder(), return "Password should not be
labelText: 'Email', greater than 15 characters";
hintText: 'Enter Email' } else
), return null;
validator: (value) { },
if (value!.isEmpty) { ),
return "* Required"; const SizedBox(height: 16,),
} else if (![Link]()) { ElevatedButton(
return "Check your email"; onPressed: () {
} else if (_formKey.currentState!.validate())
return null; {
},
), [Link](context).showSnackBar(
const SizedBox(height: 16,), const SnackBar(content:
TextFormField( Text('Processing Data')),
decoration: const InputDecoration( );
border: OutlineInputBorder(), }
labelText: 'Password', },
hintText: 'Enter secure password' child: const Text('Submit'),
), )
obscureText: true, ],
validator: (value) { ), 52
LES WIDGETS D’INPUT ET DE SÉLECTION
Form
Checkbox
LES WIDGETS D’INPUT ET DE SÉLECTION
Row(
children: [
Checkbox(
checkColor: [Link],
value: isChecked,
onChanged: (bool? value) {
setState(() {
isChecked = value!;
});
},
),
const Text("label")
],
),
54
Radio
LES WIDGETS D’INPUT ET DE SÉLECTION
55
Les widgets
de Bouton
LES WIDGETS DE BOUTON
ElevatedButton et OutlinedButton
ElevatedButton(
onPressed: () {},
style: [Link](
shape: RoundedRectangleBorder(
borderRadius: [Link](30.0),
),
),
child: const Text(' Elevated Button', style: TextStyle(fontSize:
18),)
),
const SizedBox(height: 12,),
ElevatedButton(
onPressed: () {},
child: const Text(' Elevated Button', style: TextStyle(fontSize:
18),)
),
OutlinedButton(
onPressed: () {
debugPrint('Received click');
},
child: const Text('Click Me'),
)
57
LES WIDGETS DE BOUTON
FloatingActionButton et IconButton
const SizedBox(height: 12,),
FloatingActionButton(
onPressed: () {},
child: const
Icon([Link]),
),
const SizedBox(height: 12,),
[Link](
onPressed: () {},
label: const Text("image"),
icon: const Icon([Link]),
),
IconButton(
onPressed: () {},
icon: const Icon([Link])
),
58
LES WIDGETS DE BOUTON
TextButton et InkWell
TextButton(
style: [Link](
textStyle: const TextStyle(fontSize:
20),
),
onPressed: () {},
child: const Text('Text'),
)
InkWell(
onTap: () {},
child: const
Text('Text')
)
InkWell(
onTap: () {},
child: Container(
color: [Link],
child: const Text(
'Text',
style: TextStyle(color: [Link]),
)
)
) 59
Les routes
Navigation vers une nouvelle page
ElevatedButton(
onPressed: () {
[Link](
LES ROUTES
context,
MaterialPageRoute(
builder: (context) => SecondRoute()),
);
},
child: const Text("click")
)
61
Navigation avec des routes nommées
MaterialApp(
title: 'Named Routes Demo',
initialRoute: '/',
routes: {
LES ROUTES
ElevatedButton(
onPressed: () {
[Link](context,
'/second');
},
child: const Text("click")
)
62
LES ROUTES
Revenir à la page précédente
ElevatedButton(
onPressed: () {
[Link](context);
},
child: const Text("Back")
)
63
Passage de données entre les Widgets
-
Routes nommées
Envoyer
[Link](context).pushNamed("/second", arguments: "argument
1");
LES ROUTES
Récupérer
final args =
[Link](context)!.[Link];
Net
[Link]
Model Service
[Link]
Interface
66
Architecture
RÉCUPÉRER DES DONNÉES À PARTIR D'UN SERVICE WEB
service global_widge
models pages
s ts
home 67
Model class Article {
RÉCUPÉRER DES DONNÉES À PARTIR D'UN SERVICE WEB
Article({
[Link],
[Link],
[Link],
[Link],
[Link],
[Link],
[Link],
[Link],
});
71
service
RÉCUPÉRER DES DONNÉES À PARTIR D'UN SERVICE WEB
installer la dépendance
[Link]
http
[Link]
dependencies:
run
http: ^0.13.4 flutter pub get
import 'package:http/[Link]' as
http;
72
service
RÉCUPÉRER DES DONNÉES À PARTIR D'UN SERVICE WEB
class NewsService{
Future<Data> getAll() async {
var url = [Link]('[Link]
country=us&category=business&apiKey=e0ac43cc665a48aeb762ed7dae8139e9'
);
var response = await [Link](url,);
print('Response status: ${[Link]}');
print('Response body: ${[Link]}');
return dataFromJson([Link]);
}
}
Map<String, String> queryParameters = {
"country": "us",
"category": "business",
"apiKey":
"e0ac43cc665a48aeb762ed7dae8139e9",
};
var url = Uri(
scheme: 'https',
host: '[Link]',
path: '/v2/top-headlines',
queryParameters: queryParameters,
73
);
Interface
RÉCUPÉRER DES DONNÉES À PARTIR D'UN SERVICE WEB
-
NewsService newsService= NewsService();
[Link]().then((value) {
print([Link]!.[Link]);
});
OU
Data data = await
[Link]();
OU
74
Interface
RÉCUPÉRER DES DONNÉES À PARTIR D'UN SERVICE WEB
FutureBuilder(
future: [Link](),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if ([Link]) {
return OnSucces(
data: [Link],
);
} else if ([Link]) {
return OnError(
error: [Link](),
);
} else {
return const Waiting();
}
},
) 75
Interface
RÉCUPÉRER DES DONNÉES À PARTIR D'UN SERVICE WEB
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: [Link],
children:<Widget>[
const Icon(
Icons.error_outline,
color: [Link],
size: 60,
),
Padding(
padding: const [Link](top: 16),
child: Text('Error: ${error}'),
)
]
)
);
}
}
76
Interface
RÉCUPÉRER DES DONNÉES À PARTIR D'UN SERVICE WEB
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: [Link],
children:const <Widget>[
SizedBox(
width: 60,
height: 60,
child: CircularProgressIndicator(),
),
Padding(
padding: [Link](top: 16),
child: Text('Awaiting result...'),
)
]
)
);
}
}
77
Interface
RÉCUPÉRER DES DONNÉES À PARTIR D'UN SERVICE WEB
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: [Link],
children: <Widget>[
const Icon(
Icons.check_circle_outline,
color: [Link],
size: 60,
),
Padding(
padding: const [Link](top: 16),
child: Text('Result: $
{[Link]!.[Link]}'),
)
]
),
);
}
} 78
Firebase Cloud
Firestore
Créer un projet Firebase
80
Intégration de Firebase avec Android, iOS et Web
81
Configuration de la plate-forme Web
82
Configuration de la plate-forme Web
83
Configuration de la plate-forme Web
84
Activer Cloud Firestore
• Vous pouvez activer la base de données Firestore en sélectionnant Firestore dans le
menu de gauche, puis en cliquant sur Créer une base de données.
85
Ajout de Firebase à Flutter
86
Initialisation
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/[Link]';
89
Afficher liste des documents
import 'package:cloud_firestore/cloud_firestore.dart';
FutureBuilder(
future: [Link]("articles")
.orderBy("createdAt",descending: true)
.get(),
builder: (context, snapshot) {
if([Link]){
return OnError(message: [Link]());
} else if([Link]){
QuerySnapshot collection = [Link] as QuerySnapshot;
return BuildListArticle(collection: collection);
} else {
return const Center(
child: CircularProgressIndicator()
);
}
},
)
90
Afficher liste des documents
import 'package:cloud_firestore/cloud_firestore.dart';
StreamBuilder(
stream: [Link]("articles")
.orderBy("createdAt",descending: true)
.snapshots(includeMetadataChanges: true),
builder: (context, snapshot) {
if([Link]){
return OnError(message: [Link]());
} else if([Link]){
QuerySnapshot collection = [Link] as
QuerySnapshot;
return BuildListArticle(collection: collection);
} else {
return const Center(
child: CircularProgressIndicator()
);
}
},
)
91
Afficher liste des documents
class BuildListArticle extends StatelessWidget {
const BuildListArticle({
Key? key,
required [Link],
}) : super(key: key);
@override
Widget build(BuildContext context) {
return [Link](
itemCount: [Link],
itemBuilder: (context, index) {
Article article = [Link]([Link][index]);
return ItemCard(article: article);
},
);
}
}
92
fficher liste des documents
class ItemCard extends StatelessWidget {
const ItemCard({
Key? key,
required [Link],
}) : super(key: key);
final Article article;
@override
Widget build(BuildContext context) {
return Padding(
padding: const [Link](8.0),
child: Card(
elevation: 8,
child: Column(
children: [
Align(
alignment: [Link],
child: Text([Link]!.toIso8601String()),
),
ListTile(
title: Text([Link]),
subtitle: Text([Link]),
),
],
)
),
);
}
} 93
Afficher liste des documents
class OnError extends StatelessWidget {
final String message;
const OnError({
Key? key,
required String [Link],
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Text(message,
style: const TextStyle(color: [Link]),
),
);
}
}
94
MERCI
POUR VOTRE
AT T E N T I O N