Les applications complexes et la gestion de l’état vont de pair
Lorsqu’il s’agit de créer des applications mobiles, le framework Flutter est rapidement devenu l’un des choix les plus populaires parmi les développeurs. Et pour cause : Flutter offre une expérience rapide et réactive aux utilisateurs, et ses widgets permettent aux développeurs de créer facilement de belles interfaces utilisateur personnalisées.
La plupart des développeurs sont tentés de sauter l’étape de gestion de l’état dans les applications Flutter. Flutter est livré avec des choses pour aider à cela, comme StreamBuilder
ou FutureBuilder
‘s, qui vous permettent de réagir aux changements d’état dans un flux ou un futur. Par eux-mêmes, cependant, cela ne suffit pas. Voyons pourquoi.
Le problème lent de sauter la gestion de l’état
Lorsque vous créez une application, vous devez garder une trace de toutes les différentes données utilisées par l’application et de la manière dont elles interagissent les unes avec les autres. C’est ce qu’on appelle la « gestion de l’état ». Si vous ne gérez pas correctement l’état de votre application, vous vous retrouverez avec un gâchis de code difficile à comprendre et encore plus difficile à maintenir.
Voici quelques-uns des problèmes auxquels vous pouvez vous attendre si vous n’utilisez pas la gestion d’état dans votre application Flutter :
- Difficulté de débogage : sans une gestion d’état appropriée, il peut être difficile de connaître l’état actuel de l’application et comment elle y est arrivée. Cela rend difficile le débogage des problèmes qui surviennent.
- Interface utilisateur incohérente : si différentes parties de l’application mettent à jour l’état indépendamment, cela peut entraîner une interface utilisateur incohérente. L’utilisateur peut voir différentes parties de l’application dans différents états, ce qui peut être déroutant et frustrant.
- La base de code devient plus difficile à raisonner : sans une gestion d’état appropriée, la base de code peut rapidement devenir un enchevêtrement de dépendances et d’interactions. Cela rend plus difficile la compréhension du fonctionnement des différentes parties de l’application et peut entraîner des bogues et des plantages.
Certains développeurs pourraient penser qu’ils peuvent s’en tirer sans gestion d’état parce que l’application est petite, mais c’est naïf. Même les petites applications peuvent rapidement devenir complexes, et à mesure que l’application se développe, le manque de gestion de l’état deviendra de plus en plus problématique.
Sûr de dire que ne pas utiliser la gestion d’état dans une application Flutter est une recette pour un désastre. Cela conduira à une base de code difficile à comprendre, à maintenir et sujette aux bogues. Si vous souhaitez créer une application rapide, réactive et facile à comprendre et à entretenir, vous devez gérer correctement l’état de votre application.
Pire encore, au moment où vous souhaiteriez avoir utilisé une solution de gestion d’état comme BLoC, vous avez probablement déjà affaire à une application énorme qui a une base de code très difficile à lire. Étant donné que la gestion de l’état est un composant essentiel de votre application Flutter, vous ne pouvez pas l’ajouter après coup sans la réécrire.
Le cas de BLoC
Voyons d’abord pourquoi utiliser un modèle comme BLoC est une bonne idée. Dans toute application, il existe trois composants principaux : l’interface utilisateur (UI), la logique métier et les données. L’interface utilisateur est ce que l’utilisateur voit et avec lequel il interagit, la logique métier est le code qui définit le comportement de l’application et les données sont les informations que l’application utilise et affiche.
Le problème avec beaucoup de développement d’applications est que ces trois composants sont souvent étroitement liés. Habituellement, le résultat est qu’une application commence simplement, mais à mesure qu’elle devient plus complexe, la base de code devient souvent de plus en plus compliquée et difficile à lire, ce qui entraîne un « code spaghetti ». Cela peut rendre difficile la modification d’une partie de l’application sans affecter les autres. Il peut également être difficile de tester les différentes parties de l’application séparément.
Le modèle BLoC résout ce problème en séparant la logique métier de l’interface utilisateur et des données. La logique métier est placée dans un composant séparé, le BLoC, qui peut être facilement testé et réutilisé. Cette séparation des préoccupations permet une base de code plus modulaire et maintenable.
Mais pourquoi utiliser BLoC spécifiquement dans Flutter ? D’une part, les widgets de Flutter sont construits à l’aide d’un modèle de programmation réactif, ce qui en fait un ajustement naturel pour le modèle BLoC. Avec BLoC, vous pouvez facilement mettre à jour l’interface utilisateur en réponse aux modifications de la logique métier et vice versa.
De plus, le modèle BLoC fonctionne bien avec le problème de « gestion d’état » qui peut survenir dans les applications Flutter plus grandes. Sans une gestion d’état appropriée, il peut être difficile de garder une trace de toutes les différentes données qu’une application utilise et de la façon dont elles interagissent les unes avec les autres. BLoC fournit un moyen clair de gérer et de mettre à jour l’état de l’application, ce qui facilite le raisonnement et le débogage.
Enfin, le pattern BLoC devient de plus en plus populaire dans la communauté Flutter. Cela signifie que de nombreuses ressources sont disponibles pour apprendre et mettre en œuvre BLoC, ainsi qu’un nombre croissant de bibliothèques et d’outils qui le prennent en charge.
Gestion des dépendances dans BLoC
Dans d’autres frameworks, comme le développement Android natif, l’injection de dépendances est gérée par des bibliothèques comme Dagger (ou, du moins, je l’espère, car cela fait un certain temps que je n’ai pas fait de développement Android natif 😅). L’idée est que vous enregistrez vos dépendances dans un conteneur de dépendances, puis résolvez ces dépendances lorsque vous en avez besoin.
Avec BLoC, vous pouvez utiliser RepositoryProviders
pour obtenir un effet similaire. Il fournit un moyen de créer facilement un référentiel (une classe qui gère les tâches liées aux données telles que la récupération de données à partir d’une source distante) disponible dans toute l’application sans avoir à le transmettre à plusieurs niveaux de widgets.
Utiliser RepositoryProvider
, vous devez d’abord définir le référentiel que vous souhaitez utiliser. Dans mon cas, ces services ont atteint un certain objectif — ils n’ont pas eu à étendre une autre classe ou quelque chose comme ça.
Une fois que vous avez défini votre dépôt, vous pouvez utiliser le RepositoryProvider
widget pour le rendre disponible dans toute l’application. Cela se fait généralement assez tôt dans l’initialisation de l’application. Dans une application que j’ai produite, cela ressemble à ceci :
class MyApp extends StatelessWidget {
// Define the services that our app uses. We're not initialising them just yet
// so we use the late keyword.
late CommunicationService _communicationService;
late LicenseService _licenseService;
late ActionsService _actionsService;
late CrashService _crashService;
late PreferenceService _preferencesService;
late DatabaseService _databaseService;
late ApiActionService _apiActionService;MyApp({Key? key}) : super(key: key) {
// Services that have no dependencies, we initialise first.
_licenseService = LicenseService();
_crashService = CrashService();
_preferencesService = PreferenceService();
_databaseService = DatabaseService();
// Subsequent services that have dependencies are initialised second.
_actionsService = ActionsService(_databaseService);
_apiActionService = ApiActionService(_databaseService);
// Depending on what platform we are targeting, use a specific service.
if (defaultTargetPlatform == TargetPlatform.windows || defaultTargetPlatform == TargetPlatform.macOS) {
_communicationService = DesktopCommunicationService(_actionsService, _preferencesService);
} else {
_communicationService = AndroidIosCommunicationService(_actionsService, _preferencesService);
}
}
// Now create our app with these dependencies
@override
Widget build(BuildContext context) {
return MultiRepositoryProvider(
providers: [
RepositoryProvider(
create: (context) => _actionsService,
),
RepositoryProvider(
create: (context) => _communicationService,
),
RepositoryProvider(
create: (context) => _licenseService,
),
RepositoryProvider(
create: (context) => _crashService,
),
RepositoryProvider(
create: (context) => _preferencesService,
),
RepositoryProvider(
create: (context) => _databaseService,
),
RepositoryProvider(
create: (context) => _apiActionService,
)
],
child: MaterialApp(
... Create app here and resolve via RepositoryProvider as required ...
);
}
Une fois que le référentiel est disponible dans toute l’application, vous pouvez facilement y accéder à partir de n’importe quel widget qui en a besoin en utilisant le RepositoryProvider.of(context)
méthode. Par exemple, dans une classe BLoC, vous pouvez accéder au référentiel en appelant RepositoryProvider.of<MyRepository>(context)
.
Emballer
Il existe de nombreuses excellentes raisons pour lesquelles vous devriez choisir une solution de gestion d’état pour votre application Flutter et pourquoi ce choix devrait être BLoC. Le faire tôt dans le développement de votre application peut vous éviter bien des soucis.
Qu’utilisez-vous pour gérer l’état de votre application ? Est-ce BLoC ou Riverpod, ou êtes-vous simplement en train de l’ignorer pour l’instant ? Faites-le moi savoir dans les commentaires, et faites-moi également savoir si vous avez besoin d’aide pour un certain aspect.