Flutter pour un développeur iOS — Créer une app de blog Partie 1
Introduction
Développeur iOS natif, j’ai découvert Flutter complètement par hasard, au détour d’un article sur Medium. Etonnement, alors que je n’avais pas accroché à l’approche de React Native, ce nouveau framework m’a vraiment intéressé. S’il manque encore beaucoup de choses avant de pouvoir vraiment faire concurrence à React Native ou au développement natif, Flutter est très prometteur.
Dans cette série d’article, je vous propose de réaliser une application de blog, qui ressemblera à ce que vous pouvez voir ci-dessous :
L’interface est inspirée d’un mockup de Ghani Pradita, que je vous invite à aller voir.
L’idée va être de réaliser ce projet avec l’approche d’un développeur iOS, et de voir comment ce qui se fait nativement avec Swift se fait ici avec Flutter, pour voir quelles différences et similitudes on peut trouver.
Je pars du principe que vous êtes prêt à créer un projet Flutter. Je vais également réaliser des comparaisons avec ce qui se passe en développement iOS, mais il n’est absolument pas nécessaire de connaître ce domaine pour comprendre l’article.
Si vous n’avez pas encore installé Flutter, vous pouvez suivre la doc Google qui très bien faite pour mettre le pied à l’étrier.
Dans cet article, je vous propose de mettre en place le projet, et de créer la couverture de l’article.
On abordera les points suivants :
- le fonctionnement très schématique des interfaces sur Flutter
- créer un écran
- structurer différents éléments graphiques
- utiliser des assets (images et polices)
- créer des widgets
Mise en place du projet
Commençons par créer un projet et par nettoyer tout ce qui ne nous servira pas :
- tout le dossier test
- tout le contenu du fichier main.dart
Ensuite, importons les assets que nous allons utiliser dans ce projet (soit dans cette partie, soit dans la suite). Vous pouvez télécharger les assets ici.
Importez le dosser assets à la racine de votre projet.
On utilisera deux types d’assets : des images et des polices. Mais avant de pouvoir les utiliser effectivement dans l’application, il va falloir les importer. Pour ce faire, il faut modifier le fichier pubspec.yaml qui gère les assets et les dépendances (comme un Podfile si vous avez utilisé CocoaPods pour un projet iOS).
Importer des images
Insérez le contenu suivant :
flutter:
uses-material-design: true
assets:
- assets/images/article_image.jpg
- assets/images/profile_pic.png
Il existe une manière de déclarer plusieurs formats pour une même image, mais ce ne sera pas utile pour notre projet.
Importer des polices
Sous les lignes que l’on vient d’insérer, ajouter le code suivant :
fonts:
- family: Gotham
fonts:
- asset: assets/fonts/Gotham-Black.otf
weight: 600
- asset: assets/fonts/Gotham-Medium.otf
weight: 400
- family: Caecilia
fonts:
- asset: assets/fonts/CaeciliaLTStd-Roman.otf
weight: 400
C’est assez explicite, vous ne trouvez pas ?
Remarquez la propriété weight qui suit le nom de chaque fichier. Cela nous permettra d’accéder aux différents poids d’une même famille de police sans mentionner le nom du fichier, nous y reviendrons.
Voilà, on dispose à présent de tous les assets dont nous aurons besoin pour ce projet. Je vous propose maintenant de créer les modèles de données pour notre application.
Modèles de données
L’application que l’on est en train de réaliser est une application de blog.
On aura donc des articles qui contiendront :
- un titre
- un nom d’auteur
- un tag définissant la catégorie de l’article
- du contenu
- une image
(et des commentaires, mais nous nous en occuperons dans un prochain article)
Sous le dossier lib, créons donc un dossier model qui contiendra notre fichier article.dart. Dans ce fichier, on va définir la classe Article qui regroupera les informations pour un article.
Voilà à quoi ressemble notre fichier :
import 'package:meta/meta.dart';
import 'package:flutter/material.dart';
class Article {
String title;
String author;
String tag;
String text;
Image image;
Article(
{@required this.title,
@required this.author,
@required this.tag,
@required this.text,
@required this.image});
}
Passons en revue ce qu’on vient d’écrire :
- On importe le package meta pour une raison que je vais expliciter juste en-dessous, ainsi que le package material pour avoir accès au widget Image (équivalent des UIImageView).
- On déclare une classe Article, et on liste les différents attributs de cette classe. La syntaxe est simple : on donne le type puis le nom de l’attribut. Jusque là, tout va bien.
- Enfin, on a écrit le constructeur de cette classe. Il y a plusieurs façons d’écrire un constructeur, néanmoins, je vous montre une syntaxe que vous pourriez retrouver souvent. Notons plusieurs choses :
- les paramètres this.<something> sont directement les attributs de l’instance : en appelant le constructeur et en passant « Titre » en paramètre, l’attribut title prendra cette chaîne sans qu’il ne faille rajouter du code.
- les paramètres sont ici entre accolades, et précédés du mot-clé @required. Les paramètres entre accolades sont nommés, mais le mot-clé les rend, comme son nom l’indique, requis. Je fais cela pour que le nom du paramètre apparaisse explicitement à l’appel du constructeur, comme ceci :
Article(title: null, author: null, tag: null, text: null, image: null)
Au-lieu de :
Article(null, null, null, null, null)
Si le constructeur était sans accolades et required.
Les deux ont la même fonction, mais je trouve la première syntaxe plus explicite.
Voilà, on a maintenant notre classe Article. Il nous reste une dernière chose à mettre en place avant d’attaquer le vif du sujet, à savoir l’interface.
Injecteur de données
Pour ce projet, on ne va pas travailler avec une API qui nous fournira de véritables articles, ni de véritables commentaires ou profils. On va donc pour le moment créer des données « placeholders », et un injecteur de données (que l’on pourrait modifier plus tard pour y intégrer de vraies données).
L’injecteur de données sera un objet simple dont le but sera de permettre d’accéder à un article. Ce faisant, on peut supposer qu’une requête a été faite, et qu’on a récupéré un article à afficher.
Sous le dossier lib, créez un dossier injector qui contiendra le fichier placeholder_data.dart. Dans ce fichier, écrivons le code suivant :
class ArticleInjector {
static final ArticleInjector _injector = new ArticleInjector._internal();
static ArticleInjector get shared {
return _injector;
}
Article get article {
return placeholderArticle;
}
ArticleInjector._internal();
}
On vient donc de créer une classe ArticleInjector assez simple. Remarquons les choses suivantes :
- Cette classe dispose d’une instance statique initialisée via notre constructeur privé _internal.
- Notez également qu’il n’y a pas de mot-clé pour déclarer un objet privé dans une classe, contrairement au private en Swift. A la place, si le nom de l’attribut est précédé d’un underscore (« _ »), alors il est privé.
- On a déclaré un getter nommé shared. La structure est assez claire, avec le mot-clé get avant le nom du getter.
- L’autre getter article va renvoyer simplement un article que l’on va écrire nous même à la suite de la classe.
Donc, rajoutons le placeholderArticle qui nous servira pour ce projet.
En développement iOS, lorsque l’on crée une classe dans un fichier, elle est immédiatement accessible dans le reste du projet (entendu qu’elle ne soit pas privée bien sûr). Ce n’est pas le cas avec Flutter malheureusement. Il faut importer chaque fichier qui nous sera utile explicitement.
Par conséquent, puisque l’on va déclarer une instance de la classe Article que nous avons créée tout à l’heure, il nous faut importer notre fichier article.dart. Pour ce faire, il faut indiquer le chemin du fichier. Il y a deux manières de faire ça :
- en chemin relatif
- en chemin absolu
Je préfère les chemins absolus, car ceux-ci ne dépendent que de la position des fichiers que l’on importe, et non de ceux dans lesquels on importe en plus.
Ajoutons donc le code suivant en haut de notre fichier placeholder_data.dart :
import 'package:blog_app/model/article.dart';
import 'package:flutter/material.dart';
On importe aussi material.dart car on va insérer un widget Image dans notre Article. Voici le code que vous pouvez utiliser.
// Contenu texte qui nous servira de placeholderString articleContent =
"""Saepissime igitur mihi de amicitia cogitanti maxime illud considerandum videri solet, utrum propter imbecillitatem atque inopiam desiderata sit amicitia, ut dandis recipiendisque meritis quod quisque minus per se ipse posset, id acciperet ab alio vicissimque redderet, an esset hoc quidem proprium amicitiae, sed antiquior et pulchrior et magis a natura ipsa profecta alia causa. Amor enim, ex quo amicitia nominata est, princeps est ad benevolentiam coniungendam. Nam utilitates quidem etiam ab iis percipiuntur saepe qui simulatione amicitiae coluntur et observantur temporis causa, in amicitia autem nihil fictum est, nihil simulatum et, quidquid est, id est verum et voluntarium.
Postremo ad id indignitatis est ventum, ut cum peregrini ob formidatam haut ita dudum alimentorum inopiam pellerentur ab urbe praecipites, sectatoribus disciplinarum liberalium inpendio paucis sine respiratione ulla extrusis, tenerentur minimarum adseclae veri, quique id simularunt ad tempus, et tria milia saltatricum ne interpellata quidem cum choris totidemque remanerent magistris.
Saraceni tamen nec amici nobis umquam nec hostes optandi, ultro citroque discursantes quicquid inveniri poterat momento temporis parvi vastabant milvorum rapacium similes, qui si praedam dispexerint celsius, volatu rapiunt celeri, aut nisi impetraverint, non inmorantur.
Quare talis improborum consensio non modo excusatione amicitiae tegenda non est sed potius supplicio omni vindicanda est, ut ne quis concessum putet amicum vel bellum patriae inferentem sequi; quod quidem, ut res ire coepit, haud scio an aliquando futurum sit. Mihi autem non minori curae est, qualis res publica post mortem meam futura, quam qualis hodie sit.
Oportunum est, ut arbitror, explanare nunc causam, quae ad exitium praecipitem Aginatium inpulit iam inde a priscis maioribus nobilem, ut locuta est pertinacior fama. nec enim super hoc ulla documentorum rata est fides.""";Article placeholderArticle = new Article(
title: "The dark side of the digital nomad",
author: "Mark Manson",
tag: "Travel",
text: articleContent,
image:
new Image.asset("assets/images/article_image.jpg", fit: BoxFit.cover));
On déclare donc un contenu d’article, et un article lui-même. Grâce au constructeur que je vous détaillais tout à l’heure tandis qu’on codait la classe Article, nos paramètres sont nommés, comme on aurait tendance à voir le plus souvent en Swift.
Notre Image est créée avec le constructeur dédié .asset() qui va aller piocher dans les assets que l’on a importé tout à l’heure, en indiquant le chemin absolu (c’est à dire le même chemin qu’indiqué dans pubspec.yaml).
Image est équivalent à UIImageView en Swift. Par conséquent, le paramètre fit permet d’ajuster la manière qu’aura l’image de s’ajuster dans l’espace qui lui est attribuée, comme contentMode en natif. On fait donc appel à l’énumération BoxFit qui définit ce comportement.
Voilà, à présent, on a tout ce qu’il nous faut, et on peut donc attaquer la création de l’interface ! Sauf que …
Avant de coder l’interface…
Côté iOS, il y a un grand débat quant à savoir s’il vaut mieux créer les interfaces directement via le code, uniquement avec les Storyboards, ou avec un mélange des deux. Pour ma part, j’ai pris l’habitude de créer la disposition générale et le layout sur Storyboard, puis de styliser les objets via le code.
Sauf que voilà, avec Flutter, tout se fait avec le code, le layout se fait naturellement, et on n’a pas les mêmes objets à disposition avec material.dart qu’avec UIKit.
Par conséquent, avant de se plonger dans le code, prenons un moment pour observer notre interface et voir comment tout ça se décompose et se hiérarchise pour savoir ce qu’il vaudra coder. Car la construction ne se fait pas de la même manière entre iOS et Flutter, allons-y étape par étape.
Reprenons notre mockup final :
Elle se découpe dans un premier temps entre :
- en rouge : l’ensemble image + éléments de l’article ;
- en bleu : la zone blanche en bas de l’écran avec deux boutons ;
C’est donc une colonne de deux éléments, telle que la barre du bas a une hauteur fixe, et l’ensemble image + textes prend toute la place restante.
Regardons plus précisément notre barre du bas :
On a un conteneur, avec un encart de quelques pixels tout autour (en vert). A l’intérieur de ce encart, deux boutons espacés le plus possible l’un de l’autre.
C’est donc une ligne de trois éléments : un bouton, un espacement et un autre bouton. Cette ligne se trouve à l’intérieur d’un encart, elle même à l’intérieur d’un conteneur.
Enfin, regardons notre header d’article :
On a une pile de deux éléments : une image étendue le plus possible, en-dessous d’une colonne de trois textes différents (zone grise), séparés les uns des autres par des encarts. Enfin, le dernier texte est à l’intérieur d’un encart, lui-même à l’intérieur d’un conteneur blanc.
Voilà comment se découpe notre interface, des éléments les plus grands aux éléments les plus petits, en des termes que l’on va pouvoir (enfin) traduire en code.
Notez que les interfaces en Flutter jouent énormément sur l’inbrication, bien plus qu’en iOS. Ce qui rend la gestion des éléments superposés un petit peu subtile.
Codons les widgets
Et procédons de la même manière que pour notre analyse de l’interface, en commençant du plus grand au plus petit.
Par conséquent, notre premier widget sera … la page elle-même. Car contrairement à UIKit où on utiliser les UIViewController comme page, sur Flutter, les pages sont elles-mêmesdes Widget (c’est à dire qu’elles tiennent plus du UIView que du Controller, si tant est qu’on puisse faire l’analogie).
Page d’article
Sous lib, créez un nouveau dossier pages qui contiendra le fichier article_page.dart.
Maintenant, à chaque fois que l’on s’apprête à créer un nouveau Widget, il faut se demander si :
- son interface est susceptible de changer si les données qu’on lui a passé,
- les données qu’on lui a passé sont susceptibles de changer.
Pour notre page, ni l’un ni l’autre puisque l’article ne va pas changer en cours de route. La page est donc un widget qui n’a pas besoin d’être associé à un State, c’est donc un héritier de StatelessWidget.
Jetons un oeil à la structure de base de notre ArticlePage :
import 'package:flutter/material.dart';
import 'package:blog_app/model/article.dart';
class ArticlePage extends StatelessWidget {
final Article _article;
ArticlePage(this._article);
@override
Widget build(BuildContext context) {
return null;
}
}
Les quelques premières lignes sont assez communes. Juste une remarque sur le constructeur de notre classe, qui cette fois est très simplement écrit. Le paramètre _article est nécessaire, mais non nommé à l’appel du constructeur, nous y reviendrons.
La méthode build est là où la magie s’opère, puisque c’est via cette méthode que la page se construit en contenant le widget retourné par la fonction.
Réglage du main
Pour afficher notre écran, il nous faut la déclarer comme étant la première page à afficher. Pour cela, retournons dans le fichier main.dart que nous avions effacé à la création du projet, et insérons le code suivant :
import 'package:blog_app/pages/article_page.dart';
import 'package:blog_app/injector/placeholder_data.dart';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
title: "Blog app",
home: new ArticlePage(ArticleInjector.shared.article)));
}
Le main() est la première fonction qui est appelée au lancement du programme (un concept devenu encapsulé côté iOS). Une seule action est à réaliser dans cette fonction : commencer à faire tourner l’application. C’est ce à quoi sert runApp().
On donne à notre application un titre, et le premier écran à afficher, qui est notre ArticlePage (remarquez le constructeur sans paramètre nommé).
A présent, on peut lancer l’application, mais il ne se passera pas grand chose, puisque vous recevrez une erreur, faute de contenu à afficher.
Structure de la page
Ajoutons nos premiers éléments à notre ArticlePage. On a dit que notre page était composée en premier lieu d’une colonne de deux éléments :
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Column(
children: <Widget>[
new Placeholder(),
new Placeholder(),
],
));
}
Le Scaffold permet de contenir une page et de poser les bonnes contraintes. Et le corps de notre page est composé, comme nous l’avons d’une, d’une Column de deux éléments (des placeholders pour le moment) en ordre de position verticale.
Complétons un petit peu notre interface initiale pour que nous puissions enfin lancer l’application et avoir un visuel acceptable.
Nous avons mentionné que le premier élément de la colonne devait prendre le plus de place qu’il lui est possible de prendre. En iOS natif, il aurait fallu créer les contraintes nécessaires. Avec Flutter, il suffit de placer cet élément dans un Expanded. Beaucoup plus explicite.
Nous avons également mentionné que tout ce qui se trouve dans la barre du bas serait contenu dans un widget. On va donc placer un Container, dans lequel se trouvera juste un Placeholder pour le moment. Comme notre Container doit avoir une hauteur définie, on va lui spécifier une valeur pour l’attribut height.
Voilà ce qu’on a jusque-là :
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Column(
children: <Widget>[
// ¬ ARTICLE HEADER
new Expanded(
child: new Placeholder(),
),
// ¬ BOTTOM BAR
new Container(
height: 70.0,
child: new Placeholder(),
)
],
));
}
Et maintenant, si on exécute l’application, voilà ce qu’on obtient :
Avec quelques lignes de code, le layout est fait. Mais continuons… Puisque l’on a commencé à faire tourner l’application, vous pouvez en profiter pour tirer partie du « Hot Reload » pour recharger l’app et voir votre avancée de manière atomique.
Barre du bas
Avant de faire grossir build() de trop, créons une fonction interne à la classe pour construire la barre du bas, et remplacez le Container que l’on a créé par la fonction, comme ceci :
Widget _buildBottomBar() {
return null;
}
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Column(
children: <Widget>[
// ¬ ARTICLE HEADER
new Expanded(
child: new Placeholder(),
),
// ¬ BOTTOM BAR
// On a retiré le Container, qui a été remplacé par la nouvelle fonction
_buildBottomBar(),
],
));
}
Notre barre du bas sera donc composée d’un Container, dans lequel se trouve une zone tampon, au-milieu de laquelle on aura une ligne de trois éléments : un bouton, un espace flexible et un autre bouton.
Voilà ce que ça donne en code :
Widget _buildBottomBar() {
return new Container(
height: 70.0,
color: Colors.white,
// Définit la zone tampon entre la bordure du Container et les Widgets qui seront imbriqués dedans
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
child: new Row(
children: <Widget>[
new IconButton(
icon: new Icon(Icons.close),
onPressed: () => print("Close button touched")),
new Expanded(child: new Container()),
new IconButton(
icon: new Icon(Icons.comment),
onPressed: () => print("Comments button touched")),
],
),
);
}
On retrouve bien la traduction en code de ce que l’on a écrit. Notez que notre espace flexible est un Container que l’on a étendu au maximum en le plaçant dans un Expanded. C’est comme si on avait contraint une UIView à prendre le plus de largeur possible, mais en plus explicite.
Pour les boutons, nous avons utilisé un objet déjà existant (que nous remplacerons dans un article suivant), le widget IconButton.
Notez le fait qu’avec deux lignes, on lui a donné une icône (c’est à dire qu’à l’intérieur du bouton, on a placé un widget Icon dont l’image est un des Icons fourni avec material.dart), ainsi qu’une action à réaliser lorsqu’il est touché. On gagne en clarté par rapport au addTarget côté natif iOS, car ici, le callback du bouton est directement accessible depuis son constructeur.
Si on reconstruit notre application, c’est déjà bien mieux :
Header de l’article
Créons notre dernier widget.
Comme celui-ci sera un peu plus conséquent en terme de code, plaçons-le dans un fichier dédié. Sous lib, créez un dossier widgets qui contiendra notre fichier article_cover.dart. Dans ce fichier, écrivez le code minimum pour notre nouveau Widget ArticleCover, qui n’a pas non plus besoin d’un State puisque le contenu de l’article n’est pas censé changer :
import 'package:flutter/material.dart';
import 'package:blog_app/model/article.dart';
class ArticleCover extends StatelessWidget {
final Article _article;
ArticleCover(this._article);
@override
Widget build(BuildContext context) {
// TODO: implement build
return null;
}
}
On lui passe en paramètre de son constructeur l’article dont il devra afficher le titre, le nom de l’auteur, l’image et la catégorie.
Revenons rapidement dans article_page.dart et remplaçons le Placeholder restant par notre nouveau widget en ajoutant le code suivant :
import 'package:blog_app/widgets/article_cover.dart';[...]class ArticlePage extends StatelessWidget {
[...]
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Column(
children: <Widget>[
new Expanded(
// On remplace ici le placeholder par notre nouveau widget
child: new ArticleCover(_article),
),
_buildBottomBar(),
],
));
}
}
Ce qui est vraiment pratique, c’est que ce faisant, on va pouvoir continuer d’utiliser la fonction « Hot Reload » pour voir comment évolue notre interface, sans avoir à casser le workflow durant la compilation.
De retour dans article_cover.dart.
On avait déterminé que ce widget serait composé d’une pile contenant l’un sur l’autre une image et une colonne de textes dans un encart.
Voici ce que ça donne pour la pile ne contenant que l’image pour l’instant :
@override
Widget build(BuildContext context) {
return new Stack(
fit: StackFit.expand,
children: <Widget>[
// On utilise l'image que l'on a créée avec l'article
_article.image
],
);
}
On a donc placé un Stack qui a la caractéristique d’étendre ses enfants le plus possible dans l’espace disponible, comme le précise StackFit.expand.
Il ne nous reste plus qu’à ajouter par dessus notre image le Container dans lequel se trouve un Padding à l’intérieur duquel se trouve une Column de trois Text séparés par des Padding verticaux uniquement.
Voyons ce que donne le code pour cela, dans le corps de la classe ArticleCover :
Widget _buildArticleTextList() {
return new Container(
padding: const EdgeInsets.only(
bottom: 40.0, left: 30.0, right: 60.0, top: 30.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
// ¬ AUTHOR TEXT
new Text(
_article.author.toUpperCase(),
), // Text
new Padding(padding: const EdgeInsets.only(top: 20.0)),
// ¬ ARTICLE TITLE TEXT
new Text(
_article.title.toUpperCase(),
overflow: TextOverflow.clip,
),
new Padding(padding: const EdgeInsets.only(top: 30.0)),
// ¬ TAG TEXT dans un Container pour le fond blanc
new Container(
padding: const EdgeInsets.all(10.0),
color: Colors.white,
child: new Text(
_article.tag.toUpperCase(),
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return new Stack(
fit: StackFit.expand,
children: <Widget>[
_article.image,
// Nouvelle fonction pour ne pas surcharger le code
_buildArticleTextList()
],
);
}
Remarquez que la Column possède deux attributs d’alignement pour fixer ses enfants respectivement le plus à gauche et le plus bas qu’il est possible de le faire.
Le texte concernant le titre de l’article possède un attribut supplémentaire : overflow. Il définit le comportement qui devra être adopté si le contenu prend trop de place par rapport à l’espace disponible. Ici, avec .clip, il sera simplement tronqué.
Pour le reste, prenez le temps de lire tranquillement chaque ligne.
C’est presque ça, sauf que pour l’instant, nos textes ne sont pas très esthétiques….
Styliser les textes
Il ne reste plus qu’à exploiter les polices que l’on a importé au tout début.
Pour ce faire, créons un dernier dossier ui_model sous lib dans lequel on ajoutera le fichier text_styles.dart.
Dans ce fichier, on va créer des styles de texte que l’on pourra réutiliser dans l’application.
Voici ce que ça donne :
import 'package:flutter/material.dart';
TextStyle articleCoverTitleStyle = new TextStyle(
fontFamily: 'Gotham',
fontWeight: FontWeight.w500,
fontSize: 45.0,
color: Colors.white,
letterSpacing: 1.5,
wordSpacing: 1.7,
height: 1.35);
TextStyle articleCoverAuthorStyle = new TextStyle(
fontFamily: 'Gotham',
fontWeight: FontWeight.w400,
fontSize: 16.0,
color: Colors.white,
);
TextStyle articleCoverTagStyle = new TextStyle(
fontFamily: 'Gotham',
fontWeight: FontWeight.w500,
fontSize: 14.0,
color: Colors.black,
letterSpacing: 2.0);
La syntaxe est assez explicite en soi.
L’intérêt d’avoir attribué à chaque fichier de police un poids dans pubspec.yaml, c’est que l’on peut se contenter d’appeler le nom de la famille et le poids pour invoquer tel ou tel fichier.
Il ne reste plus qu’à intégrer ces styles dans article_cover.dart de la manière suivante :
import 'package:blog_app/ui_model/text_styles.dart';[...]// Pour chaque Text, on rajoute le paramètre 'style', et on utilise les styles que l'on a défini précédemment.Widget _buildArticleTextList() {
return new Container(
padding: const EdgeInsets.only(
bottom: 40.0, left: 30.0, right: 60.0, top: 30.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
// ¬ AUTHOR TEXT
new Text(
_article.author.toUpperCase(),
style: articleCoverAuthorStyle,
), // Text
new Padding(padding: const EdgeInsets.only(top: 20.0)),
// ¬ ARTICLE TITLE TEXT
new Text(
_article.title.toUpperCase(),
style: articleCoverTitleStyle,
overflow: TextOverflow.clip,
),
new Padding(padding: const EdgeInsets.only(top: 30.0)),
// ¬ TAG TEXT
new Container(
padding: const EdgeInsets.all(10.0),
color: Colors.white,
child: new Text(
_article.tag.toUpperCase(),
style: articleCoverTagStyle,
),
),
],
),
);
}
Et enfin, on a fini :
Le projet final
Vous pouvez retrouver le projet final ici.
Conclusion
Avec ces premiers Widgets, on a donc vu comment se construisait une interface avec Flutter … Et à quel point le processus est différent d’iOS en natif, a fortiori lorsque l’on tire partie de la puissance des Storyboards. Cela demande donc de visualiser la hiérarchie des objets dans un autre paradigme.
Puisque tout est à coder en terme d’interface, il est très facile d’obtenir un code très complexe à lire, avec des nombreux niveaux d’imbrication. Il est donc important de scinder les widgets en de petites unités beaucoup plus simples.
Grâce au « Hot Reload », le flow de développement quant à lui peut être très rapide, un vrai plus par rapport au développement en Swift sur iOS.
Outre quelques généralités en Dart, on a donc vu :
- comment structurer un écran,
- quelques objets spécifiques à Flutter qui substituent le layout manuel en natif,
- comment styliser du texte,
- le fonctionnement des boutons
Pour la partie 2, nous verrons l’équivalent du UIScrollView, du UITableView, comment créer des Widgets plus spécifiques, comment naviguer entre deux pages.
Voilà !
Si vous avez des suggestions à faire, ou bien si vous trouvez une erreur quelconque, ou même si vous avez simplement apprécié l’article, n’hésitez pas à me le mentionner.
Partie 2
Vous pouvez à présent trouver la partie 2 ici.