Comment concevoir des projets de qualité en Flutter ?

Flutter est un framework de Google utilisé pour construire des applications compilées nativement pour les appareils mobiles, web, desktop et embarqués avec une seule base de code. Vous pouvez facilement démarrer un nouveau projet Flutter en suivant le guide : Démarrage – Flutter.

Lorsque l’on parle d’architecture pour les applications Flutter, il existe de nombreuses façons de structurer votre code et vos widgets. L’objectif principal de cette proposition est de faciliter le développement de votre projet et de garder une bonne organisation en même temps.

Dans ce post, nous utiliserons les paquets suivants :

Dio : Un client HTTP puissant pour Dart

GetIt : Un localisateur de services simple pour Flutter et Dart

Flutter BLoC : Un package qui aide à implémenter le pattern BLoC

 

Création d’une structure de base

Lorsque vous démarrez un nouveau projet à partir de zéro, vous pouvez avoir une structure de projet comme celle-ci :

 

.

├─── android

├── assets

├─── build

├─── ios

├─── lib

├─── test

├─── README.md

├─── analysis_options.yaml

├─── pubspec.lock

├─── pubspec.yaml

 

Commençons donc à mettre en place nos répertoires dans le dossier lib comme ci-dessous :

 

.

├─── composants

├── constants

├─── core

├─── interfaces

├─── middlewares

├─── modules

├─── utils

├─── main.dart

└─── routes.dart

Maintenant, notre structure de base est en place ! Ne vous inquiétez pas de tous ces dossiers et fichiers, tout au long des prochains sujets, nous aborderons le but de chacun d’entre eux et pourquoi nous avons besoin de tous.

 

Les composants : Widgets atomiques

Comme les projets React/React Native ici à Cheesecake Labs, nous travaillons avec Atomic System/Components pour rendre nos widgets plus réutilisables et organisés.

widgets-atomiques

source : Brad Frost (2016)

 

De plus, nous structurons les composants comme cela :

Atomes : Les composants fondamentaux comme les textes personnalisés, les boutons, les icônes et la typographie.

Molécules : Pour faire simple, ce sont des groupes d’atomes.

Organismes : Ils sont une association de molécules, comme une liste personnalisée ou un formulaire.

Templates : Ils sont la structure finale composée par les organismes. Nos pages/écrans utiliseront un template pour définir le contenu réel.

En considérant les informations ci-dessus, nous pouvons organiser le dossier des composants comme suit :

 

.

├─── atomes

├── molécules

├── organismes

└─── modèles

 

Que sont les constantes ?

Les constantes représente le code qui est immuable dans votre application. Ce qui suit peut être considéré comme une constante :

  • Couleurs
  • Styles
  • Les nombres spécifiques

Par exemple, si vous devez modifier la couleur de l’application ou la taille de la police que vous utilisez, vous devrez remplacer l’ancienne valeur de la constante dans un seul fichier.

Le noyau de l’application

La couche centrale est responsable des services que l’application utilisera tout au long de son cycle de vie. Il existe deux services de base que nous mettons généralement en place :

 

L’injection de dépendances

Nous pouvons créer un fichier dart et enregistrer nos instances comme suit :

 

GetIt getIt = GetIt.instance ;

 

void startGetItModules() {

  _networkModules() ;

  …

}

 

void _networkModules() {

  getIt.registerSingleton<Dio>(

    HttpHelper(dotenv.env[‘BASE_URL’] !).addInterceptor(AuthInterceptor()).dio,

  ) ;

}

 

Cela nous permettra de fournir des dépendances à nos classes, de garder les instances faciles à changer pour différentes implémentations, et d’aider à séparer les couches de modules.

 

Navigation

Pour nous aider à travailler plus efficacement avec la navigation, créons un navigateur personnalisé :

 

class RouteNavigator {

  static final GlobalKey<NavigatorState> navigatorKey =

       GlobalKey<NavigatorState>() ;

 

  static Future<dynamic> ? pushReplacementNamed<Arguments>(

  {chaîne obligatoire routeName, Arguments ? arguments}) {

    return navigatorKey.currentState

        ?.pushReplacementNamed(routeName, arguments : arguments) ;

  }

 

  static void pop() {

    navigatorKey.currentState ?.pop() ;

  }

  …

}

 

Et pour commencer à utiliser ce navigateur, vous devez passer la navigatorKey à votre MaterialApp.

 

class MyApp extends StatelessWidget {

  // Ce widget est la racine de votre application.

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      title : ‘My App’,

      navigatorKey : RouteNavigator.navigatorKey,

      onGenerateRoute : (routeSettings) {

         return MaterialPageRoute(builder : routes[routeSettings.name] !);

      },

      …

    ) ;

  }

}

 

En utilisant cette classe, nous serons en mesure de naviguer dans l’application sans aucun contexte requis, juste en appelant :

 

RouteNavigator.pushNamed(…).

 

Interfaces

Lorsque nous développons une application, nous avons généralement besoin de communiquer avec une API ou une ressource externe, comme une base de données locale et des capteurs. Le dossier interfaces est donc l’endroit où vous stockez les fichiers qui vous aideront dans cette communication.

Un exemple courant est la création d’une instance Dio pour travailler avec des requêtes HTTP :

 

class HttpHelper {

  final String _url ;

  late final BaseOptions _options ;

  late final Dio _dio ;

  Dio get dio => _dio ;

 

  HttpHelper(this._url) {

    _buildBaseOptions() ;

    _buildHttp() ;

  }

 

  void _buildBaseOptions() {

    _options = BaseOptions(

      baseUrl : _url,

      responseType : ResponseType.json,

    ) ;

  }

 

  void _buildHttp() {

    _dio = Dio(_options) ;

  }

 

  HttpHelper addInterceptor(Interceptor interceptor) {

    _dio.interceptors.add(interceptor) ;

 

    retourne ceci ;

  }

}

 

Middlewares

Les middlewares sont chargés de fournir une extension à un événement ou une action de votre application. Il peut s’agir d’un intercepteur de requête HTTP, d’un enregistreur ou de quelque chose de similaire.

Vous pouvez voir ici un exemple d’intergiciel qui génère une erreur personnalisée lorsque la demande à une API renvoie le code d’état 401 :

 

class AuthInterceptor extends Interceptor {

  @override

  void onError(DioError err, ErrorInterceptorHandler handler) {

    if (err.response != null) {

      if (err.response ?.statusCode == 401) {

        throw UnauthorizedException() ;

      }

    }

    handler.next(err) ;

  }

}

 

Modules : Une approche d’architecture hexagonale

Ce dossier est le plus important, car il contient tous les modules qui mettent en œuvre nos fonctionnalités et leurs règles de fonctionnement. Nous utiliserons ici une approche d’architecture hexagonale qui nous permettra de séparer notre couche d’application de la couche de domaine et de la couche d’infrastructure. Ainsi, chaque module sera structuré selon l’architecture suivante :

modules

Avec cette architecture, le code sera plus organisé, mais nous devons également le modulariser pour faciliter l’ajout d’une nouvelle fonctionnalité, la modification des anciennes et rendre le code plus réutilisable.

Ainsi, nous vous suggérons de structurer les modules comme :

 

.

└─── fonctionnalité

  ├── blocs

  │

  ├── données

  │ └─── datasource //abstract datasources

  │ └─── repositories //implémentation de repositories abstraites.

  │

  ├─── datasource //implémentation de datasources abstraites

  │

  ├── domaine

  │ └─── entités

  │ └─── dépôts //référentiels abstraits

  │

  └─── écrans

 

Séparer les modules de cette manière nous permet d’utiliser des implémentations différentes pour chaque couche. Nous pouvons, par exemple, créer plusieurs écrans différents avec la même logique de domaine ou même changer tous les fournisseurs de sources de données sans affecter l’ensemble de l’application. Ainsi, notre application est séparée en morceaux indépendants qui peuvent fonctionner individuellement.

De plus, cette approche favorise la testabilité, ce qui est un bon point pour construire un projet fiable et améliorer la qualité du code.

 

Le mot de la fin

Lorsque vous avez affaire à des projets complexes qui contiennent de nombreuses fonctionnalités, cette architecture est d’une grande aide lorsqu’il s’agit de tout garder organisé, facile à modifier, et extrêmement simple à maintenir et à tester le code tout au long du processus de développement.

Il s’agit simplement d’un moyen efficace que nous avons trouvé pour structurer nos projets Flutter chez Agily et nous espérons que cela vous aidera également.

Vous voulez en savoir plus sur la façon d’utiliser Flutter pour faire sortir votre projet de la planche à dessin ? Contactez-nous !

Vous avez un projet en tête ?

Discutons-en ! 

Découvrez nos autres articles de blog

WordPress Vs JAMstack

WordPress Vs JAMstack

On départage Wordpress vs Jamstack pour déterminer quelle est la meilleure option pour créer un site web rapide, sécurisé et simple à développer.

lire plus
Loading...