De iOS à Flutter : un REX et des mythes

Pour mon premier article de 2023, j’ai envie de parler d’un changement survenu l’année dernière : mon passage d’iOS à Flutter. Mon intention ? Partager mon REX et au passage démystifier quelques légendes.

Je trouve intéressant de partager cette expérience parce que je vois qu’il y a encore beaucoup de réticences à passer d’une technologie mobile native (un développeur écrit directement du « code iOS » ou du « code Android ») à une technologie cross-plateform (un développeur écrit « un seul code » qui permettra d’avoir à la fois une application Android et iOS). Allons-y !

Pourquoi changer ?

J’ai envie de commencer en abordant cette question : pourquoi j’ai décidé de passer du dev iOS au dev Flutter. Début 2022, mon projet client¹ se termine et je cherche donc le prochain. J’ai des pistes pour des projets iOS et également une piste pour un projet Flutter. Cette fameuse piste Flutter va doublement retenir mon attention.

C’est une typologie de projet que j’aime beaucoup. L’équipe est pluridisciplinaire, le métier fait partie intégrante de l’équipe, on a accès aux utilisateurs et on fait régulièrement des entretiens et des ateliers avec eux, on veut livrer de la valeur qui fait sens aux utilisateurs, le produit a du sens, il y a une grande attention sur la qualité de code, et j’en passe.

Et c’est en Flutter. Oui, ça m’intéressait déjà un peu. Il faut savoir aussi que j’aime la tech. Au sens large. Je ne m’identifie pas à un framework. Je joue régulièrement avec d’autres langages et concepts en mob-programming pour apprendre de nouvelles choses. J’aime le code, le craft, la tech, être un software engineer.

Je voyais donc ce changement comme une belle opportunité de travailler professionnellement dans un nouvel écosystème, avec en plus un projet qui a beaucoup de sens pour moi. J’ai foncé.

1. Je parle de “projet client” parce que je travaille à ce moment-là à BENEXT / OCTO qui sont des ESN.

Des interrogations avant

À cet instant, je n’avais quasiment jamais fait ni de Flutter, ni de Dart (le langage du framework). Seulement une heure lors d’un mob-programming organisé à BENEXT en 2018. Et c’était assez laborieux. J’étais marqué par la lourdeur d’un vieux langage et la difficulté de faire des choses simples. Je me suis même dit “ahah, je ne ferais jamais du Flutter !”. Ne jamais dire jamais. :)

J’avais quelques interrogations au début. Sur le langage déjà, vu que j’avais un passif avec Dart et que j’adorais ce que Swift proposait en termes de simplicité, de lisibilité, de modernité. Mais quelques tours dans les documentations et des discussions avec des développeurs m’ont vite fait rendre compte que le langage avait changé depuis. Et en bien.

Je me posais aussi des questions sur le fait d’écrire du code qui doit être compatible avec deux plateformes différentes. À quel point j’allais devoir corriger des bugs mystiques qui ne se produiraient que sur iOS ou que sur Android ? À quel point j’allais devoir écrire du code spécifique à une plateforme ? À quel point il y avait des complexités autour de la compilation ?

J’ai aujourd’hui une première réponse : tout va bien. On a livré de nombreuses fonctionnalités sans se confronter à de gros problèmes dû aux plateformes natives. Rentrons maintenant davantage dans les détails.

Le langage Dart

Dart me faisait penser à du vieux Java lors de mon premier contact. Mais ça, c’était avant. Aujourd’hui, il a beaucoup changé. Il a pris un tournant davantage moderne et il a de moins en moins à rougir face à du Swift ou du Kotlin. Il y a par exemple des notions d’Optional, de Future, d’async/await, d’Isolates (vulgairement, de l’actor model) et bien d’autres choses visible dans le language tour.

Il faut également savoir que le langage continue d’évoluer avec un bon rythme. On peut d’ailleurs lire et créer des propositions d’évolution directement dans le repository GitHub de Dart. En voici trois que j’attends avec impatience : les sealed types et les patterns pour une future version, et les data classes / structs que j’espère voir embarqué le plus vite possible !

J’aimerais que Dart s’améliore sur la gestion de l’immutabilité et qu’il permette une approche plus fonctionnelle. Et un peu de réduction de boilerplate qu’il faut encore écrire à la main (equatable, parsing json, copy). Il y a la possibilité d’ajouter freezed pour donner quelques pouvoirs supplémentaires à Dart, mais ça vient avec les inconvénients de la génération automatique de code.

Globalement, il y a l’essentiel dans le langage et je trouve la lecture et l’écriture de code Dart plutôt agréable - même si j’ai toujours mon coup de cœur pour Swift et Kotlin. Voici un court sample venant de Dartpad, un éditeur en ligne qui permet de jouer avec Dart et Flutter :

void main(List<String> arguments) async {
  final url = Uri.https(
    'www.googleapis.com',
    '/books/v1/volumes',
    {'q': '{http}'},
  );

  final response = await http.get(url);

  if (response.statusCode == 200) {
    final jsonResponse = convert.jsonDecode(response.body);
    final itemCount = jsonResponse['totalItems'];
    print('Number of books about HTTP: $itemCount.');
  } else {
    print('Request failed with status: ${response.statusCode}.');
  }
}

Les tests

La facilité d’écrire des tests et la rapidité d’éxécution est pour moi très importante - j’aime me faire guider par les tests quand je développe. Avec Dart, je suis ravi : j’obtiens un feedback très rapidement. Je ne suis pas autant freiné pour faire du TDD qu’avec de gros projets iOS qui mettent bien trop longtemps à compiler, se préparer à s’exécuter, et faire tourner la suite de tests.

Avec Flutter aussi je suis surpris. On peut faire des tests de widgets (UI) assez facilement, et surtout, ils sont exécutés rapidement. Sans besoin de lancer un simulateur, sans besoin de lancer l’application entière.

La UI

C’est simple, j’ai repris goût à faire de la UI. C’était une de mes douleurs avec iOS. Enfin plus précisément, UIKit était une de mes douleurs. On va me dire « mais t’as SwiftUI ! ». Ok cool, mais ce n’est pas parce que Apple nous offre à nous, développeurs, ce super nouveau framework UI, qu’on peut l’utiliser dans le projet.

On se heurte vite au deployment target, et là-dessus, tous les contextes projet sont différents. Certaines applications doivent maintenir des versions d’iOS antérieures à SwiftUI (grande base utilisateurs, utilité publique, argent). Et quand bien même le deployment target est assez élevé, on ne passe pas à SwiftUI facilement sur un projet de 500.000 lignes de code - les PoC et les projets from scratch, c’est pas automatique.

Et donc Flutter dans l’histoire ? C’est de la declarative UI, de base. Pour les amateurs de SwiftUI pour iOS ou de Jetpack Compose pour Android, eh bien c’est la même idée. On retrouve cette manière simple de créer des vues (nommées Widget) et de faire de la composition.

class HomePage extends StatelessWidget {
  
  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text(
          'Hello, World!',
        ),
      ),
    );
  }
}

Un avantage par rapport à SwiftUI (et UIKit), c’est la très grande diversité de Widget embarqué dans le SDK Flutter. On veut créer un composant qui permet de collapse / expand ? Il y a un widget pour ça. On veut faire de l’autocomplete ? Il y a un widget pour ça. On arrête enfin de recréer certaines roues basiques et on se concentre sur notre produit. Il y a plus d’une centaine de widgets, ils sont bien documentés et en plus superbement expliqués dans de très courtes vidéos : Flutter Widget of the Week.

J’avance donc très vite sur la UI, et sans grande douleur car tout est pensé pour bien se composer, et est assez configurable. Je trouve en tout cas qu’on a plus de paramètres sur les widgets natifs de Flutter que ce que nous donne Apple avec le SDK iOS - dès qu’on sort de la sacro-sainte guideline, il faut sortir les rames.

Le Hot Reload

Pour moi, le Hot Reload, c’est une killer feature en termes d’expérience pour le développeur. Lorsque l’on modifie du code, on obtient instantanément les changements dans l’application qui est en cours de fonctionnement. C’est un cran au-dessus des SwiftUI Preview et du Swift Playground réunit.

Ça marche du tonnerre. Il faut bien comprendre que l’application tourne dans un simulateur ou un vrai device, et que le nouveau code source est injecté à chaque fois qu’on le modifie. Pour expérimenter, pour construire de la UI rapidement, pour fixer des bugs, c’est juste génial.

Ça fonctionne de base sur l’ensemble du projet et ça ne bug quasiment jamais. On peut modifier de la UI et voir le résultat tout comme on peut modifier de la logique (un repository, un use case, un simple if) et essayer immédiatement le changement.

Les IDEs

Tout ça c’est cool, mais dans quoi on code ? Deux principaux choix reviennent souvent : Android Studio avec la puissance de Jetbrains qui se cache derrière, et Visual Studio Code avec sa légèreté et sa grande modularité. Pour avoir joué avec les deux, ils font bien le job.

Quel bonheur d’avoir enfin accès à un grand panel d’outils pour nous assister dans la modification de code et dans le refactoring ! Extract, invert if, wrap et unwrap de widget, etc. Malheureusement, je nous trouve très pauvres côté iOS entre Xcode qui n’a pas grand chose et la fin du support d’AppCode.

J’ai une préférence pour Visual Studio Code actuellement. J’ai changé récemment, mais je préfère déjà son côté plus léger, son efficacité, et sa modularité qui me permet de le configurer comme je le souhaite pour faire du Flutter et d’autres technos. Bon à savoir si on préfère d’autres IDEs, le LSP de Dart est disponible pour l’utiliser dans n’importe quel logiciel gérant ce protocole.

La gestion des versions

En voici une autre belle expérience pour le développeur : on a très facilement accès aux nouveautés du SDK de Dart et du SDK de Flutter, et il n’y a pas à se soucier du deployment target. C’est particulièrement tranchant avec iOS. Chaque année, la grande conférence Apple WWDC pour les développeurs était un kiff - avec les nouveautés annoncées sur le SDK iOS - suivi d’un dur retour à la réalité - il allait falloir attendre 3 ou 4 ans avant de pouvoir en profiter sur le projet client.

J’ai fais un peu d’Android avant de faire de l’iOS, et je lorgne depuis des années sur leurs libs de rétrocompatibilité (Apple, si tu lis ces lignes). Mais avec Flutter, le problème disparaît : le SDK Flutter est une dépendance au projet iOS comme n’importe quel autre package. Il suffit de mettre à jour le SDK. Pas besoin d’attendre une nouvelle version de Xcode, de macOS, ou de changer le deployment target - celle-ci reste compatible jusqu’à iOS 11.

Les performances

Attaquons un mythe assez tenace : « quand c’est cross-plateform, c’est nul en perf ». C’était peut-être vrai avant avec des technos comme Phonegap et des vieux téléphones, mais aujourd’hui les solutions ont changé et le hardware également. Il faut actualiser nos modèles mentaux.

Flutter est conçu pour tourner à 60FPS et même à 120FPS pour les appareils compatibles. En cuisine, il n’y ni Webview ni association “mon Button Flutter va se transformer réellement en un UIButton iOS” mais un moteur de rendu 2D écrit en C++ : Skia. À noter que depuis 2020 Skia s’appuie sur Metal, l’API graphique très performante proposée par Apple.

Tout est donc dessiné. L’équipe de Flutter a entièrement refait l’ensemble des composants graphiques. L’arbre des widgets est assez intelligent pour ne redessiner uniquement ce qui a changé sur l’écran. Et avec la declarative UI proposée par Flutter, nous, développeurs, avons la main sur la finesse des composants que nous pouvons créer pour optimiser ces changements. Ça fonctionne donc très bien.

Natif versus Hybride

On ne reprendrait pas un peu d’un autre mythe ? « Le cross-plateform c’est pas adapté à de vrais projets, c’est bien juste pour faire des petites apps toutes simples. ». Un contre-exemple au hasard : SNCF Connect. Cette application est complexe, il y a beaucoup de règles métiers et applicatives, un tas de fonctionnalités, des millions d’utilisateurs. Et elle est en Flutter. Et ce n’est pas la seule.

Je trouve que le choix du cross-plateform est de plus en plus légitime aujourd’hui. Il faut se poser la question, il faut challenger les décisions. C’est peut-être un non sens de pousser absolument du développement natif dans certains contextes. On répond à des besoins métiers et utilisateurs, pas seulement à un kiff de développeur.

Pour avoir travaillé sur de multiples applications grands publics, je trouve qu’une solution comme Flutter répond très bien aux besoins fonctionnels. Quand on prend un peu de recul sur les applications qu’on fabrique ou qu’on utilise, elles sont majoritairement assez classiques. Des listes dynamiques de contenu / d’articles / de résultats, des paniers, des parcours de recherche, des pages de détails, des favoris, des notifications, … Rien qui puisse faire peur à Flutter. Aujourd’hui, on est en 2023. On a des solutions performantes permettant de concevoir énormément de fonctionnalités. Le temps a commencé à les éprouver et les communautés sont fortes.

Il reste peu de cas où un développement natif serait absolument nécessaire (dans le sens où on ne peut faire sans). Flutter couvre beaucoup de fonctionnalités, et la communauté des développeurs ne cessent d’ajouter des packages pour combler les manques. Et quand bien même on tombe sur un besoin qui ne peut être fait qu’en natif, il est possible de faire des morceaux de code en natif au sein d’un projet Flutter. Au moins, on a toujours 98% de l’application cross-plateform et on développe en natif seulement sur les 2% où c’est absolu nécessaire.

Quelques précisions sur le code natif au sein d’un projet Flutter. On peut modifier le projet natif lui-même. Il y a un sous-dossier ios dans l’arborscence du projet Flutter dans lequel on y retrouve nos fichiers classiques comme le .xcodeproj ou le AppDelegate. Il est donc possible d’ouvrir ce projet avec Xcode et d’y créer un widget iOS 14 par exemple, qui ne peut être écrit qu’en SwiftUI. On peut également créer un pont entre du code natif et du code Flutter, afin de récupérer des données ou d’exécuter des actions sur la plateforme native depuis du code Flutter. Lire le pourcentage de la batterie ou ouvrir une popup système de partage par exemple - sachant que la communauté a déjà créé une multitude de packages. Flutter appelle ça des platform channels.

C’est plus difficile de ne jurer que par un double développement natif aujourd’hui au vu des vastes possibilités offertes par le cross-platform. Et il faut aussi prendre en compte que l’on veut réduire les coûts. Dans une période où la forte croissance et les forts investissements yeux fermés ne sont plus la normalité, ça pèse davantage dans la balance.

La courbe d’apprentissage

Je commence à te convaincre, tu veux te lancer sur Flutter. Mais tu as tout appris sur iOS. Tu as investi du temps et de l’énergie sur ce framework. Tu ne veux pas avoir l’impression de revenir à zéro, d’être un développeur junior, de perdre ce que tu as capitalisé. Ce sont des affirmations que j’ai entendu, et je ne suis pas en phase avec.

Parce que iOS ce n’est au final qu’un framework, et Swift qu’un langage. Tu n’es pas que Développeur iOS, tu es aussi Développeur. Tu as également appris des tas de concepts et méthodes plus large dans l’univers de la tech : de la qualité de code, du refactoring, du design, de l’architecture, de la programmation objet et fonctionnelle, des patterns, de l’algo, etc. Sans oublier tout ce qui va au-delà de la technique : travailler en équipe, avancer par petites étapes, obtenir du feedback rapidement, communiquer, et bien d’autres.

Toutes ces compétences et ces connaissances ne disparaissent pas et sont utiles tout le long de la carrière d’un développeur, peu importe son environnement. On ne recule pas d’un point de vue développeur. On va seulement travailler une nouvelle expertise. Et ça, c’est cool ! Découvrir de nouveaux concepts, un autre langage, un autre éco-système, apprendre et nourrir sa curiosité, ajouter une corde à son arc. C’est ce que je trouve passionant avec notre métier, il y a tant de choses à découvrir.

Plus on change et plus on est à l’aise dans un autre environnement. On retombe très vite sur nos pattes quand on utilise un langage inconnu parce qu’on reconnaît des concepts qu’on a pu découvrir dans d’autres langages et framework auparavant. Les katas de code sont d’ailleurs un excellent moyen pour jouer avec plusieurs langages et concepts. Ce qui m’amène à la courbe d’apprentissage de Flutter. À quoi ressemble-t-elle ? Eh bah rien de bien méchant.

Dart c’est une sorte de Java moderne. Quand on vient de Swift, on ne devrait pas être trop perdu. On est à l’aise avec la programmation orientée objet et le typage fort, on connaît les extensions, les génériques, et même le async/await depuis peu. On s’y retrouve très vite dans les concepts, il faut principalement s’habituer à quelques changements de syntaxe. Je prévois de dédier un de mes prochains articles à un quick start comme je l’ai fait avec Kotlin, pour me rassembler les idées, et pour aider ceux que ça intéresse.

Et pour ce qui est de Flutter, la barrière est mince. Passer de iOS/Android à Flutter nous fait rester dans le même monde : le mobile. On est déjà à l’aise avec les concepts et les contraintes spécifiques au mobile : la navigation, les écrans réactifs, les notifications, la connectivité, l’ergonomie, etc. Ce sont les mêmes en Flutter. On va juste changer le comment, parce que c’est un autre framework, mais on s’y retrouve vite. Encore plus lorsque l’on a déjà utilisé SwiftUI qui est de la declarative UI comme Flutter.

Deux liens intéressants provenant de la documentation Flutter :


Bref

D’un point de vue expérience développeur, j’aime beaucoup. C’est agréable de développer en Flutter, c’est efficace, on avance vite, on obtient du feedback rapidement, l’éco-système évolue régulièrement et je ne me sens pas bridé. Côté expérience utilisateur, on obtient une application performante qu’on peut rendre très proche du natif. C’est donc plutôt du win-win.

Ce n’est pas la techno parfaite - inutile de perdre du temps à chercher, ça n’existe pas. Mais ça me semble faire sens aujourd’hui. Le développement de deux applications natives n’est pas la seule option à envisager pour créer de nouveaux produits. C’est pour cela que j’avais envie d’écrire, ouvrir les chakras en évoquant mon REX et en abordant certains mythes. Je ne vends pas Flutter, ni ne fait un “Pour et Contre” exhaustif. Identifiez vos besoins, faites vos recherches, et expérimentez.

À bientôt :)