03 — Développement mobile
iOS, Android, multiplateforme, et les backends derrière eux
Des applications mobiles livrées à de vrais utilisateurs — et restées livrées — pendant plus de onze ans.
iOS et Android natifs dans leurs propres langages, React Native et Flutter là où ils conviennent, et les backends, caches et clouds qui font d’un téléphone plus qu’une coquille mince. La trajectoire de onze ans est ce qui rend le reste crédible.
J’ai passé plus de onze ans à construire et livrer des applications mobiles à de vrais utilisateurs — et ce seul fait est celui qui rend chaque autre affirmation de cette page digne d’être lue.
N’importe qui peut lister des frameworks. Le titre qui compte, c’est la longévité : des applications que de vraies personnes ont installées, conservées et utilisées sur iOS, Android et Windows, pendant plus d’une décennie. La trajectoire de onze ans n’est pas une ligne sur un CV — c’est la preuve que les décisions ci-dessous ont été prises sous la pression d’un logiciel qui devait continuer à fonctionner après le lancement.
Je travaille natif d’abord. Sur iOS cela signifie Swift et Objective-C avec SwiftUI, Core Data, Core Animation et SiriKit ; sur Android cela signifie Kotlin et Java contre l’Android SDK sur de nombreuses versions d’OS. Cette fluidité native est exactement ce qui fait du travail multiplateforme — React Native et Flutter à un niveau avancé, Xamarin à un niveau opérationnel — un choix délibéré plutôt qu’un moyen d’éviter une plateforme.
Et une application mobile n’est jamais seulement le client. Derrière elle se trouvent des backends en Node, Spring Boot, Django, Flask, Rails et Go, exposant REST et GraphQL, soutenus par SQLite sur l’appareil et Redis sur le serveur, déployés sur Firebase, AWS et Google Cloud. La page qui suit est toute cette surface, racontée telle que je la construis vraiment.
années à livrer des applications mobiles à de vrais utilisateurs, sur iOS, Android et Windows
plateformes natives maintenues dans leurs propres langages — Swift/Objective-C et Kotlin/Java
stacks multiplateformes que je maîtrise à un niveau avancé — React Native et Flutter
frameworks backend que j’associe aux clients mobiles, de Node à Go
Deux plateformes natives, deux stacks multiplateformes, une surface Windows.
La première décision sur tout projet mobile, c’est sur quoi construire, et c’est rarement la même réponse deux fois. La matrice ci-dessous est ma façon de raisonner : iOS et Android natifs chacun dans son propre langage, deux stacks multiplateformes que je maîtrise à un niveau avancé, et une surface Windows qui garde honnête la logique partagée. Le diagramme mappe les langages et frameworks sur les plateformes plutôt que de prétendre qu’un seul outil couvre tout.
Langages et frameworks, mappés aux plateformes
Une matrice, pas un monolithe — chaque plateforme dans le langage qu’elle récompense.
iOS reçoit Swift et Objective-C ; Android reçoit Kotlin et Java ; Windows reçoit son propre client. React Native et Flutter couvrent les stores quand l’UI ne se bat pas contre la plateforme, et la colonne de droite montre où chaque outil atterrit vraiment.
Lire la matrice, c’est le travail : c’est ainsi que je décide si une fonctionnalité est native, multiplateforme, ou un client mince sur un backend partagé, avant qu’une ligne d’UI soit écrite.
- iOS — Swift, Objective-C, SwiftUI, Core Data
- Android — Kotlin, Java, Android SDK
- Multiplateforme — React Native, Flutter (avancé)
- Windows — client bureau natif
Ce que j’écris vraiment sur chaque plateforme.
Les plateformes ne sont pas interchangeables, et l’outillage le reflète. Chaque onglet est une plateforme sur laquelle j’ai livré — les langages, les frameworks, et le jugement sur le moment où chacun gagne sa place.
iOS natif en Swift, avec Objective-C là où il vit encore
Sur iOS j’écris en Swift et je lis Objective-C, parce que les vraies bases de code qui livrent depuis des années sont rarement du Swift pur. Je construis les interfaces en SwiftUI pour les nouveaux écrans et je garde UIKit là où un flux existant en dépend, plutôt que de réécrire du code qui marche pour le plaisir d’un framework.
La persistance, c’est Core Data quand l’application possède un vrai graphe d’objets, et le mouvement, c’est Core Animation quand une transition doit se sentir native plutôt qu’approximée. SiriKit gère les intents vocaux là où ils gagnent leur place — pas sur chaque écran, seulement là où un raccourci parlé est vraiment plus rapide que de toucher.
- Swift d’abord, Objective-C lu et maintenu
- SwiftUI pour les nouveaux écrans, UIKit gardé là où il marche
- Core Data pour le graphe d’objets, Core Animation pour le mouvement
- Intents SiriKit là où la voix est réellement plus rapide
Android en Kotlin et Java, sur de nombreuses versions d’OS
Sur Android je travaille en Kotlin et Java contre l’Android SDK, et j’ai livré contre un large éventail de versions d’OS — le travail concret, c’est de supporter les appareils que les utilisateurs portent vraiment, pas seulement la dernière version. Cela signifie des décisions honnêtes de SDK minimum et tester la fragmentation, pas la supposer absente.
Les mêmes motifs se reportent : une couche de données claire, une UI consciente du cycle de vie, et la gestion explicite des choses qu’Android vous force à gérer — mort de processus, changements de configuration, et limites d’arrière-plan qui se resserrent à chaque version.
- Kotlin et Java contre l’Android SDK
- Livré sur de nombreuses versions d’OS Android
- UI consciente du cycle de vie, gestion explicite de la mort de processus
- Décisions de SDK minimum guidées par les vrais appareils
Des clients Windows aux côtés du travail mobile
Je construis aussi pour Windows, ce qui garde la surface honnête : une fonctionnalité qui doit atterrir sur iOS, Android et Windows ne peut pas s’appuyer sur les commodités d’une seule plateforme. Les parties partagées sont poussées dans le backend et les contrats, et les parties propres à chaque plateforme restent minces.
Cette discipline est la raison pour laquelle les stacks multiplateformes ci-dessous sont un choix plutôt qu’un défaut — j’ai maintenu les côtés natif et bureau, donc je sais ce que chaque abstraction m’épargne réellement.
- Clients Windows livrés aux côtés du mobile
- Logique partagée poussée vers le backend et les contrats
- Code par plateforme gardé mince et explicite
React Native et Flutter à un niveau avancé, Xamarin opérationnel
React Native et Flutter, je les maîtrise à un niveau avancé. J’y recours quand un produit a besoin qu’une équipe livre deux stores rapidement et que l’UI ne se bat pas contre la plateforme — et je descends aux modules natifs dès qu’un écran a besoin de Core Animation ou d’un capteur que le pont n’expose pas proprement.
Xamarin, je l’ai à un niveau opérationnel, ce qui compte dans les organisations orientées .NET où le reste de la stack est déjà en C#. Le point n’est pas la loyauté à un framework ; c’est d’ajuster la stack à l’équipe et au produit.
- React Native — avancé, avec modules natifs au besoin
- Flutter — avancé, une seule base de code pour deux stores
- Xamarin — niveau opérationnel, pour les équipes orientées .NET
- Descendre au natif quand le pont gêne
Swift tel qu’il se lit sur un vrai écran.
Un panneau de code est plus honnête qu’une liste de fonctionnalités — c’est la forme du travail iOS, pas une description de celui-ci.
Sur iOS je m’appuie sur SwiftUI pour les nouvelles surfaces et Core Data pour le graphe d’objets derrière elles. L’extrait ci-dessous est du genre que j’écris quotidiennement : un modèle petit et typé, un fetch que la vue peut observer, et une mise en page qui reste déclarative. C’est délibérément ordinaire — l’intérêt de la fluidité native, c’est que le code de routine soit propre, pas malin.
import SwiftUI
import CoreData
struct ContactList: View {
@FetchRequest(
sortDescriptors: [SortDescriptor(\.name)]
) private var contacts: FetchedResults<Contact>
var body: some View {
List(contacts) { contact in
VStack(alignment: .leading) {
Text(contact.name)
.font(.headline)
Text(contact.phone)
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
.animation(.easeInOut, value: contacts.count)
}
} L’ensemble de frameworks iOS
SwiftUI au-dessus, Core Data en dessous, SiriKit là où la voix est plus rapide.
Les frameworks se composent : SwiftUI rend, Core Data persiste, Core Animation gère le mouvement que SwiftUI ne gère pas, et SiriKit expose le ou les deux intents où un raccourci parlé bat vraiment le toucher. Objective-C reste dans le tableau parce que les applications de longue durée en contiennent encore, et le lire fait partie de leur maintenance.
Rien de cela n’est ajouté pour soi-même. Chaque framework est dans l’application parce qu’un problème spécifique — persistance, mouvement, voix — l’a demandé.
- SwiftUI pour les nouveaux écrans, UIKit gardé là où il marche
- Core Data pour le graphe d’objets
- Core Animation pour le mouvement au ressenti natif
- Intents SiriKit seulement là où la voix est plus rapide
Kotlin contre l’Android SDK, fragmentation comprise.
Android récompense l’explicitude. L’extrait ci-dessous est un ViewModel Kotlin exposant l’état d’UI comme un flow — conscient du cycle de vie, survit aux changements de configuration, et fait son chargement hors du thread principal. Ayant livré sur de nombreuses versions d’OS, j’écris du code Android qui suppose la mort de processus et des limites d’arrière-plan strictes plutôt que d’espérer les éviter.
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
class ContactViewModel(
private val repo: ContactRepository
) : ViewModel() {
private val _state =
MutableStateFlow<UiState>(UiState.Loading)
val state: StateFlow<UiState> = _state.asStateFlow()
init {
viewModelScope.launch {
runCatching { repo.loadContacts() }
.onSuccess { _state.value = UiState.Ready(it) }
.onFailure { _state.value = UiState.Error }
}
}
} De nombreuses versions d’OS, de vrais appareils
Supporter les téléphones que les utilisateurs portent, pas celui de la keynote.
La fragmentation Android n’est pas un problème dont se plaindre — c’est l’environnement. Le travail, c’est de choisir un SDK minimum honnête, de tester l’éventail de versions et de tailles d’écran que la base d’utilisateurs réelle fait tourner, et de gérer les règles d’arrière-plan et de permissions qui se resserrent à chaque version.
Java reste dans la boîte à outils aux côtés de Kotlin pour la même raison qu’Objective-C sur iOS : le code livré vit longtemps, et le maintenir signifie le lire couramment.
- Kotlin et Java contre l’Android SDK
- Choix honnêtes de SDK minimum
- Testé sur les versions que les utilisateurs font tourner
- Gestion explicite de l’arrière-plan et des permissions
La forme d’une application, dessinée de l’UI au magasin de données.
Une application mobile est une pile de couches claires — et la discipline, c’est de les garder claires pour que chacune puisse être testée et remplacée seule.
Quelle que soit la plateforme, l’architecture vers laquelle je me tourne est la même dans l’esprit : une couche UI qui ne fait que rendre l’état, une couche domaine qui tient la logique, une couche données qui parle au réseau et au magasin de l’appareil, et une frontière de sync entre les deux. Le diagramme montre les couches et la direction unique vers laquelle pointent les dépendances.
Garder les flèches pointant dans un sens, c’est ce qui rend l’application maintenable un an plus tard — l’UI n’atteint pas le réseau, et le réseau ne connaît pas l’UI. La même forme tient que le client soit Swift, Kotlin, React Native ou Flutter.
UI → domaine → données → magasin
Quatre couches, dépendances pointant dans un seul sens.
L’UI observe l’état et émet des intents ; la couche domaine décide ; la couche données récupère et persiste ; SQLite tient la vérité sur l’appareil et le réseau porte le reste. Chaque frontière est un endroit où je peux mettre un test ou échanger une implémentation sans que les couches au-dessus s’en aperçoivent.
Ce n’est pas de l’architecture pour elle-même. C’est la structure qui a permis à onze ans d’applications de continuer à accepter des fonctionnalités sans s’effondrer sous elles.
- L’UI rend l’état, n’atteint jamais le réseau
- La couche domaine tient la logique et les règles
- La couche données possède le réseau et la persistance de l’appareil
- SQLite comme source de vérité sur l’appareil
Le côté serveur de chaque application mobile.
Un client mobile ne vaut que le backend auquel il parle — alors je construis ce backend aussi, dans le framework qui convient à l’équipe.
Derrière les applications, j’ai livré Node/Express, Spring Boot, Django, Flask, Ruby on Rails et Go avec Gin et Gorm. Le framework est choisi pour l’équipe et le budget de latence, pas par habitude : Node là où l’équipe maîtrise JavaScript, Spring là où c’est une maison JVM, Django ou Flask pour des backends lourds en Python, Rails pour du CRUD piloté par convention, et Go là où le budget par requête est serré.
Chacun expose un contrat — REST ou GraphQL — contre lequel le client mobile est construit, persiste via des ORMs comme Sequelize et Mongoose, et cache le chemin chaud dans Redis. Le processus ci-dessous est le même quel que soit le framework au milieu.
Du contrat à un appareil synchronisé
- 01 Définir le contrat Schéma REST ou GraphQL convenu d’abord — le client et le serveur sont construits contre un contrat, pas l’un contre l’autre.
- 02 Choisir le framework Node/Express, Spring Boot, Django/Flask, Rails ou Go (Gin/Gorm) — choisi pour convenir à l’équipe et au budget de latence.
- 03 Modéliser les données ORMs — Sequelize ou Mongoose — sur le magasin relationnel ou documentaire dont le domaine a réellement besoin.
- 04 Cacher le chemin chaud Redis devant les lectures coûteuses, avec une invalidation explicite plutôt que des TTL pleins d’espoir.
- 05 Synchroniser l’appareil SQLite sur l’appareil, un protocole de sync sur le contrat, résolution de conflits définie avant d’en avoir besoin.
- 06 Livrer le pipeline Builds signés, tests automatisés, livraison aux stores — la CI qui transforme un commit en un build qu’un utilisateur peut installer.
func GetContact(c *gin.Context) {
id := c.Param("id")
if cached, err := rdb.Get(ctx, id).Result();
err == nil {
c.Data(200, "application/json",
[]byte(cached))
return
}
var contact Contact
if err := db.First(&contact, id).Error;
err != nil {
c.JSON(404, gin.H{"error": "not found"})
return
}
c.JSON(200, contact)
} Un handler Go, tel qu’il se livre
REST et GraphQL, servis vite et typés.
L’extrait est un handler Gin lisant via Gorm avec un cache Redis devant lui — la forme petite et répétable d’un endpoint sur lequel un client mobile peut compter : une réponse typée, une erreur prévisible, et un cache qui décharge la base de données sur le chemin chaud.
Que le framework soit Go, Node ou Spring, le contrat que le téléphone voit est le même. C’est l’intérêt de le convenir d’abord.
- Go avec Gin et Gorm sur les chemins à budget serré
- Redis cachant les lectures coûteuses
- Réponses typées, formes d’erreur prévisibles
- Même contrat quel que soit le framework
La stack mobile complète — du client au cloud
- Langages iOS
- Swift · Objective-C
- Frameworks iOS
- SwiftUI · UIKit · Core Data · Core Animation · SiriKit
- Langages Android
- Kotlin · Java
- Cible Android
- Android SDK, de nombreuses versions d’OS
- Multiplateforme
- React Native · Flutter (avancé) · Xamarin (opérationnel)
- Bureau
- Clients Windows
- Backends
- Node/Express · Spring Boot · Django/Flask · Rails · Go (Gin/Gorm)
- APIs
- REST · GraphQL
- Sur appareil / données
- SQLite · Redis · Sequelize · Mongoose
- Cloud mobile
- Firebase · AWS · Google Cloud
- Métier du frontend
- HTML5 · CSS3 · SASS/SCSS · JS ES6+ · TypeScript
Quand le réseau échoue — et sur un téléphone, il le fera.
Un téléphone traverse tunnels, ascenseurs et zones mortes. Une application qui suppose la connectivité est une application qui reste bloquée sur un spinner devant un vrai utilisateur.
Je construis des applications mobiles offline-first : l’appareil écrit immédiatement dans SQLite et met le changement en file pour sync, de sorte que l’utilisateur n’attend jamais le réseau pour voir sa propre action prendre effet. Quand la connectivité revient, la file se vide sur le contrat REST ou GraphQL, et les conflits sont résolus par une politique décidée par entité avant même d’en avoir besoin.
Le diagramme trace cette boucle — écriture locale, file, sync, réconciliation — et le mode de défaillance est la partie qui compte : l’application dégrade en lecture seule plutôt que de perdre une écriture en silence. C’est la différence entre une application en laquelle les utilisateurs ont confiance et une qu’ils abandonnent.
Écriture locale → file → sync → réconcilier
L’appareil est la source de vérité jusqu’à ce que le serveur soit d’accord.
Chaque écriture atterrit d’abord dans SQLite et est reflétée dans une file de sync. Une sync en arrière-plan vide la file contre le serveur, le serveur réconcilie en utilisant des lectures soutenues par Redis et le magasin mappé par l’ORM, et l’état résolu revient à l’appareil. L’utilisateur voit un retour local instantané et une cohérence à terme, pas un spinner.
Concevoir la politique de conflits à l’avance — last-write-wins, fusion ou manuelle — c’est ce qui garde la boucle honnête quand deux appareils éditent le même enregistrement.
- Écriture locale dans SQLite avant tout appel réseau
- Changement mis en file, vidé quand la connectivité revient
- Politique de conflits définie par entité à l’avance
- Dégrader en lecture seule, ne jamais perdre une écriture
Offline-first — le contrat avec le réseau
- Magasin sur appareil
- SQLite — la source de vérité hors ligne
- Chemin d’écriture
- Écriture locale d’abord, mise en file pour sync
- Transport de sync
- REST ou GraphQL sur le contrat convenu
- Politique de conflits
- Définie par entité avant d’en avoir besoin
- Cache serveur
- Redis devant les lectures coûteuses
- Magasin serveur
- Relationnel ou documentaire, via Sequelize ou Mongoose
- Mode de défaillance
- Dégrader en lecture seule, jamais perdre une écriture en silence
L’utilisateur ne devrait jamais attendre le réseau pour voir sa propre action. Écrire localement, synchroniser en arrière-plan, et ne jamais perdre une écriture — c’est toute la discipline offline-first.
Firebase, AWS et Google Cloud — choisis par adéquation.
Le cloud n’est pas une seule décision. Une petite équipe a besoin d’un backend pour hier ; un produit qui scale a besoin de contrôle sur son calcul. Chaque onglet est une plateforme sur laquelle je déploie des backends mobiles, et le jugement sur le moment où chacune est la bonne réponse.
Firebase quand une petite équipe a besoin d’un backend pour hier
Firebase gagne sa place quand un produit mobile a besoin d’authentification, d’un magasin de données synchronisé et de push sans monter d’infrastructure d’abord. J’utilise Auth pour la connexion, Firestore pour les données structurées et interrogeables, et la Realtime Database là où le motif d’accès est réellement un arbre vivant plutôt que des documents.
La discipline, c’est de savoir où Firebase cesse d’être la bonne réponse — des règles de sécurité qui doivent encoder une vraie autorisation, et des coûts de lecture qui croissent avec un modèle de données naïf. Je conçois les documents autour des requêtes, pas l’inverse.
- Auth pour la connexion, Firestore pour les données structurées
- Realtime Database là où un arbre vivant convient au motif d’accès
- Règles de sécurité traitées comme une vraie autorisation
- Documents conçus autour des requêtes
AWS pour les backends qui dépassent un BaaS
Quand un produit a besoin de contrôle sur son calcul, je le fais tourner sur AWS — Lambda pour les charges orientées événements et en pics, EC2 là où un processus de longue durée ou un runtime spécifique est requis, et S3 pour les médias et les charges statiques qu’un client mobile télécharge.
Le mobile change les contraintes : les démarrages à froid se ressentent sur un téléphone, les réessais d’upload doivent survivre à un lien cellulaire instable, et l’appareil ne peut pas garder une connexion ouverte comme un serveur le peut. L’architecture tient compte du réseau sur lequel l’appareil se trouve réellement.
- Lambda pour les charges orientées événements et en pics
- EC2 pour les processus de longue durée et les runtimes spécifiques
- S3 pour les médias et les charges téléchargeables
- Conçu pour un cellulaire instable, pas un LAN de datacenter
Google Cloud là où la plateforme convient à la charge
Sur Google Cloud j’utilise App Engine pour les services gérés qui scalent sans que je surveille les instances, et Compute Engine là où une charge a besoin d’une VM complète. Il s’associe naturellement à Firebase quand une partie d’un produit est un magasin de données géré et une partie un calcul sur mesure.
Le choix entre clouds est rarement idéologique. C’est de savoir quels services gérés enlèvent le plus de travail opérationnel à cette équipe tout en gardant dans le budget la latence que le client mobile perçoit.
- App Engine pour les services gérés à autoscaling
- Compute Engine là où une VM complète est requise
- S’associe à Firebase pour des backends mixtes gérés/sur mesure
- Choisi par adéquation opérationnelle, pas par loyauté
Où le backend tourne vraiment
Géré là où cela épargne du travail, contrôlé là où il le faut.
Firebase Auth et Firestore montent un backend synchronisé sans infrastructure ; Lambda et EC2 donnent du calcul serverless et de longue durée sur AWS ; App Engine et Compute Engine font l’équivalent sur Google Cloud ; S3 tient les médias qu’un téléphone télécharge. Le diagramme mappe le client sur ces options.
La contrainte mobile traverse tout cela : les démarrages à froid se ressentent sur un téléphone, et les uploads doivent survivre à un lien cellulaire. Le cloud est choisi pour que la latence que l’utilisateur perçoit reste dans le budget.
- Firebase — Auth, Firestore, Realtime Database
- AWS — Lambda, EC2, S3
- Google Cloud — App Engine, Compute Engine
- Choisi par adéquation opérationnelle, latence dans le budget
React Native et Flutter — un choix, pas un défaut.
Les stacks multiplateformes permettent à une équipe de livrer les deux stores depuis une seule base de code, et je maîtrise React Native et Flutter à un niveau avancé. Mais la raison pour laquelle je leur fais confiance, c’est que j’ai livré natif d’abord : je sais exactement ce que le pont m’épargne et où il gêne. Quand un écran a besoin de Core Animation, d’un capteur que le pont n’expose pas proprement, ou d’un comportement de plateforme que l’abstraction masque, je descends à un module natif sans hésiter.
Xamarin se situe à un niveau opérationnel dans la boîte à outils, ce qui est la bonne réponse dans les organisations orientées .NET où le reste de la stack est déjà en C#. La décision entre eux n’est jamais une affaire de loyauté — c’est d’ajuster la stack à l’équipe et au produit, avec le natif toujours disponible en dessous.
Une base de code, deux stores, trappe d’évasion native
Logique métier partagée en haut, modules natifs là où le pont s’arrête.
Dans une application multiplateforme, la logique métier et la majeure partie de l’UI vivent dans une seule base de code partagée — React Native ou Flutter — qui rend à la fois iOS et Android. Les parties que l’abstraction ne peut servir descendent jusqu’à des modules natifs en Swift et Kotlin, de sorte que l’application n’échange jamais la capacité contre la portabilité.
Cette trappe d’évasion est toute la raison pour laquelle la fluidité native compte même sur un projet multiplateforme. La couche partagée couvre le terrain commun ; les modules natifs couvrent les bords.
- Logique et UI partagées dans une seule base de code
- Rend à la fois iOS et Android
- Modules natifs en Swift / Kotlin aux bords
- La capacité jamais échangée contre la portabilité
Le pipeline qui transforme un commit en une installation.
La CI mobile est plus dure que la CI web — il y a deux chaînes d’outils, la signature de code, et deux processus de revue de store entre le commit et l’utilisateur.
Un release mobile n’est pas un déploiement ; c’est un build, une signature et une soumission. Le pipeline que je fais tourner lint et type-check d’abord, puis construit l’archive iOS avec Xcode et la signe, construit l’APK ou AAB Android signé avec Gradle, et livre des artefacts versionnés vers TestFlight et la piste interne avant le store. La règle est simple : pas de pipeline vert, pas de release.
Le diagramme ci-dessous montre cet éclatement — un commit, deux chaînes d’outils, deux stores — parce que le coût d’une erreur de signature de code ou de version est une soumission rejetée et un jour perdu, pas un redéploiement rapide.
Commit → vérifications → deux builds → deux stores
Un commit s’éclate en deux builds signés.
Le pipeline lance les vérifications statiques une fois, puis se divise : un chemin Xcode qui construit, signe et archive pour iOS, et un chemin Gradle qui assemble et signe pour Android. Chacun produit un artefact versionné qui va vers la piste de test de son store avant le release.
Traiter le build signé comme la seule chose qui se livre — jamais une archive locale de la machine de quelqu’un — c’est ce qui garde les releases reproductibles à travers onze ans d’applications.
- Lint, type-check et tests unitaires comme porte
- Xcode signe et archive pour iOS
- Gradle assemble un APK/AAB signé pour Android
- Artefacts versionnés vers les pistes de test, puis store
CI mobile — étape par étape
- Déclencheur
- Commit sur une branche de release
- Vérifications statiques
- Lint, type-check (TypeScript), tests unitaires
- Build iOS
- Build Xcode, signature de code, archive
- Build Android
- Gradle assemble, APK/AAB signé
- Artefact
- Build versionné par plateforme
- Livraison
- TestFlight / piste interne, puis store
- Porte
- Pas de pipeline vert, pas de release
Les surfaces web qui accompagnent l’application.
Un produit mobile est rarement seulement l’application. Il y a des pages d’onboarding, des surfaces marketing, des web views embarquées et de la logique partagée qui veulent tous le même soin que le code natif. Je les construis en HTML5 et CSS3 propres, j’organise les styles avec SASS/SCSS pour que le design system reste un système, et j’écris la logique en JavaScript moderne — ES6+ et TypeScript partout où le projet tolère un système de types.
TypeScript en particulier se rembourse sur mobile : un contrat typé attrape le décalage entre client et serveur avant qu’un utilisateur ne le voie, et les mêmes types peuvent circuler du schéma GraphQL jusqu’au client React Native.
HTML5 et CSS3
Les surfaces web qui accompagnent une application mobile — pages d’onboarding, marketing, web views embarquées — construites en HTML5 et CSS3 propres plutôt que jetées dans un framework par réflexe.
SASS/SCSS
Feuilles de style organisées avec SASS/SCSS pour que le design system reste un système : variables, partials et mixins au lieu de déclarations copiées.
JavaScript ES6+
JavaScript moderne pour la logique partagée et la couche web — modules, async/await, et les fonctionnalités du langage qui rendent une base de code lisible des années plus tard.
TypeScript
TypeScript partout où le projet le tolère, parce qu’un contrat typé attrape le décalage entre client et serveur avant qu’un utilisateur ne le voie.
APIs REST
REST pour les endpoints simples en forme de ressource, avec versionnage et formes d’erreur prévisibles sur lesquelles le client mobile peut compter.
APIs GraphQL
GraphQL là où un écran mobile a besoin exactement de ses données en un aller-retour — l’aller-retour réseau qu’un téléphone paie est celui qui vaut la peine d’être économisé.
Onze ans, une décision de plateforme à la fois.
Fondation, natif, multiplateforme, backend, cloud — le même ingénieur, suivant le travail à mesure que les produits dépassaient leurs premiers mille utilisateurs.
Lu en séquence, le travail mobile est une seule trajectoire continue plutôt qu’une liste de frameworks. Il commence par onze ans de livraison à de vrais utilisateurs, s’approfondit en iOS et Android natifs, s’étend à travers React Native et Flutter là où ils conviennent, revient vers les backends qui font d’un client plus qu’une coquille, et arrive aux clouds qui le scalent.
Ce qui traverse tout cela, c’est le même refus qui parcourt le reste de mon travail : l’application et le système derrière elle sont un seul problème, pas deux passés entre deux personnes.
- Fondation Onze années de livraison à de vrais utilisateurs La trajectoire qui ancre tout le reste : plus de onze ans à construire et livrer des applications mobiles que de vraies personnes ont installées et utilisées, sur iOS, Android et Windows.
- Natif iOS et Android dans leurs propres langages Swift et Objective-C sur iOS avec SwiftUI, Core Data, Core Animation et SiriKit ; Kotlin et Java sur Android sur de nombreuses versions d’OS. La fluidité native qui rend les choix multiplateformes informés.
- Multiplateforme React Native et Flutter à un niveau avancé Une équipe livrant deux stores là où cela convient, descendant aux modules natifs là où cela ne convient pas — plus Xamarin à un niveau opérationnel pour les équipes orientées .NET.
- Backend Le côté serveur de chaque application Node/Express, Spring Boot, Django/Flask, Rails et Go (Gin/Gorm), exposant REST et GraphQL, soutenus par SQLite, Redis et ORMs — les backends qui font d’un client mobile plus qu’une coquille mince.
- Cloud Firebase, AWS et Google Cloud Magasins de données gérés, calcul serverless et stockage choisis par adéquation opérationnelle — l’infrastructure qui scale une application au-delà de ses premiers mille utilisateurs.
Les principes sous les plateformes.
Les plateformes et frameworks changent avec le produit ; les principes, non. Voici les règles que j’applique que l’application soit Swift natif, Kotlin natif, React Native ou Flutter — la partie qui a transformé onze ans de travail mobile en applications qui ont continué à fonctionner plutôt qu’en un portfolio de lancements.
La fluidité native gagne l’abstraction
Parce que j’ai livré Swift et Kotlin directement, choisir React Native ou Flutter est un compromis mesuré, pas un moyen d’éviter d’apprendre une plateforme. Je sais ce que le pont m’épargne et ce qu’il me cache.
Le contrat passe en premier
Client et serveur sont construits contre un contrat REST ou GraphQL convenu, idéalement typé de bout en bout. L’intégration est conçue avant que l’un ou l’autre côté soit écrit, pas négociée après que les deux cassent.
Supposer que le réseau échouera
Un téléphone traverse des zones mortes. L’application écrit localement dans SQLite d’abord, met en file pour sync, et dégrade en lecture seule plutôt que de perdre une écriture ou de rester bloquée sur un spinner.
Respecter les règles de la plateforme
Limites d’arrière-plan, mort de processus, revue de store, signature de code — ce ne sont pas des obstacles à contourner. Travailler avec elles, c’est ce qui garde une application installée plutôt que désinstallée.
Tester la fragmentation
De nombreuses versions d’OS Android et un éventail d’appareils iOS signifient tester ce que les utilisateurs portent vraiment, pas affirmer que la dernière version couvre tout le monde.
Onze ans, c’est le titre
Chaque affirmation ici est étayée par des applications qui ont été livrées et qui le sont restées. La longévité est la preuve — pas un framework sur une diapositive, mais du logiciel qui a survécu au contact de vrais utilisateurs.
Onze ans d’applications livrées et restées livrées, voilà le titre. Tout le reste de cette page est vrai parce que cette seule chose l’est.
Open to the right work
Si votre produit est une application mobile et le backend, le cloud et le comportement hors ligne qui la rendent fiable, c’est tout le problème que je veux.
If you are holding a problem that doesn't fit inside one field, that is the conversation I want.