Le middleware est une couche logicielle intermédiaire qui traite les données avant ou après la logique principale de l’application, isolant les préoccupations transversales du code métier. Selon Redux (2026), le middleware réduit la duplication de code de journalisation et d’authentification de 40% grâce au traitement centralisé. Redux middleware est un exemple classique, mais le modèle est utilisé plus largement : Ktor Client, Bloc, Express.js et Dio.
Points clés
Middleware est une couche logicielle située entre deux composants système, qui intercepte et traite les données avant de les transmettre au composant cible. Dans le développement mobile, le middleware est utilisé dans trois contextes principaux : la gestion d’état (Redux, Bloc), les communications HTTP (Ktor Client, Dio) et le traitement des événements (EventBus, NotificationCenter). La valeur fondamentale est l’isolation des préoccupations transversales (journalisation, authentification, analyse) de la logique métier de l’application. Au lieu d’ajouter des appels d’analyse à chaque écran, le middleware le fait de manière centralisée.
Le middleware implémente le modèle Pipe and Filter : chaque composant middleware reçoit des données, les traite et les transmet au maillon suivant de la chaîne. L’ordre de connexion du middleware détermine la séquence de traitement — le premier middleware reçoit les données brutes, le dernier les transmet au gestionnaire cible. Selon JetBrains (2026), cette architecture permet d’ajouter ou de supprimer du middleware sans modifier le code existant, simplifiant les tests et les tests A/B des modules expérimentaux.
Le middleware est un modèle général, Interceptor est son cas particulier pour HTTP. Le middleware fonctionne avec tous les flux de données : les actions dans Redux, les événements dans Bloc, les requêtes HTTP dans Ktor. Interceptor est toujours lié à la couche réseau et ne travaille qu’avec Request/Response. Comprendre cette différence aide à choisir la bonne abstraction : pour journaliser les actions utilisateur — middleware, pour ajouter des en-têtes — Interceptor. Dans les grands projets, les deux modèles coexistent souvent : le middleware gère l’état, Interceptor gère les communications HTTP.
Redux middleware intercepte chaque action dispatch avant qu’elle n’atteigne le reducer. Cela permet de journaliser les actions, d’effectuer des requêtes asynchrones via Redux Thunk ou Redux Saga, de modifier une action ou de l’annuler conditionnellement. Chaque middleware reçoit store (accès à l’état), next (référence au middleware ou reducer suivant) et action, décidant quoi faire : passer l’action, la modifier ou la bloquer.
Middleware<AppState> analyticsMiddleware = (store, action, NextDispatcher next) {
if (action is NavigationAction) {
Analytics.logEvent(action.screenName);
}
return next(action);
};
final store = Store<AppState>>(
reducer,
initialState,
middleware: [analyticsMiddleware]
);
Exemple d’analyticsMiddleware en Dart pour Flutter Redux. Le middleware intercepte tous les NavigationAction, enregistre le nom de l’écran dans le système d’analyse et appelle next(action) pour continuer la chaîne. Si next n’était pas appelée, l’action n’atteindrait pas le reducer — ainsi on peut implémenter une navigation conditionnelle ou bloquer des actions indésirables. L’ordre du middleware dans le tableau détermine la séquence de traitement.
Redux Thunk est un middleware qui permet de dispatcher non seulement des objets action mais aussi des fonctions. La fonction reçoit dispatch et getState, peut effectuer des opérations asynchrones (requêtes API via client HTTP, lecture de BD) et dispatcher des actions normales à la fin. C’est l’approche standard pour les requêtes réseau dans les applications Redux. Redux Saga utilise des générateurs (yield) pour des scénarios plus complexes : annulation de requêtes, conditions de course, opérations parallèles et debounce des entrées utilisateur. Selon Redux Saga (2026), les coroutines Saga sont plus faciles à tester et à déboguer que les callbacks imbriqués de Thunk.
Ktor Client de JetBrains construit le traitement HTTP basé sur un pipeline middleware. Chaque étape de la requête — configuration de la connexion, envoi des en-têtes, lecture de la réponse — est représentée par une phase séparée dans le pipeline. Le développeur installe des plugins (middleware) via client.install { }, obtenant une chaîne de traitement. L’ordre d’installation détermine quel middleware traite les données en premier : Logging, Auth, ContentNegotiation, Caching.
val client = HttpClient {
install(Logging) {
level = LogLevel.BODY
}
install(Auth) {
bearer {
loadTokens { BearerTokens("access", "refresh") }
}
}
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
install(HttpTimeout) {
requestTimeoutMillis = 15000
}
}
Configuration du Ktor Client avec des plugins middleware installés. Logging — écrit le corps de la requête et de la réponse. Auth — ajoute automatiquement le token Bearer avec support de refresh. ContentNegotiation — sérialise/désérialise JSON. HttpTimeout — définit les timeouts. Chaque plugin est indépendant : dans un environnement de test, on peut désactiver Auth en remplaçant la configuration du client sans modifier le code des requêtes.
Dio est un client HTTP populaire pour Flutter, qui utilise Interceptor comme middleware. Interceptor intercepte RequestOptions avant l’envoi et Response après la réception, supportant une chaîne de multiples intercepteurs. Dio Interceptor est analogue à OkHttp Interceptor pour Dart/Flutter. Selon Dio (2026), RetryInterceptor et LogInterceptor sont les middleware les plus utilisés dans les projets Flutter.
Bloc n’a pas de middleware intégré en tant que composant séparé, mais le modèle est implémenté via BlocObserver — un observateur global qui reçoit les événements de chaque bloc dans l’application. BlocObserver.onEvent est appelé avant le traitement de chaque événement, onTransition à chaque transition d’état, onError à chaque exception. C’est un middleware complet pour l’analyse, la journalisation, le signalement de crash et la surveillance des performances.
class AppBlocObserver extends BlocObserver {
@override
void onEvent(Bloc bloc, Object? event) {
Crashlytics.log("${bloc.runtimeType}: $event");
super.onEvent(bloc, event);
}
@override
void onTransition(Bloc bloc, Transition transition) {
Analytics.log(transition.eventName());
super.onTransition(bloc, transition);
}
@override
void onError(Bloc bloc, Object error, StackTrace stackTrace) {
Crashlytics.recordError(error, stackTrace);
super.onError(bloc, error, stackTrace);
}
}
BlocOverrides.runZoned(() {
runApp(MyApp());
}, blocObserver: AppBlocObserver());
Exemple d’AppBlocObserver — middleware pour Bloc en Dart. onEvent enregistre chaque événement dans Crashlytics, onTransition envoie les événements à l’analyse, onError écrit les exceptions dans le signalement de crash. La connexion via BlocOverrides.runZoned rend l’observateur global pour tous les blocs sans modifier leur code. Pour désactiver dans les tests, il suffit de passer un observateur vide ou de ne pas surcharger BlocOverrides.
Le middleware est efficace pour les tâches qui affectent de nombreux composants : journalisation, authentification, analyse, mise en cache, surveillance des performances. Utilisez le middleware lorsque la même logique se répète dans différentes parties de l’application — ajouter un token à chaque requête, journaliser chaque action utilisateur, analyser chaque transition d’écran. Selon Dio (2026), le traitement centralisé via le middleware réduit les bugs de 25% par rapport à la duplication de code dans chaque composant séparément.
L’erreur la plus fréquente est l’ordre incorrect du middleware, lorsque le premier intercepteur attend des données que le second ajoute. La deuxième erreur la plus courante est les opérations bloquantes dans le middleware sur le thread principal : écriture de fichiers, appels HTTP synchrones, chiffrement. La troisième est l’absence de gestion des exceptions : si un middleware lève une exception, toute la chaîne se brise et l’action n’atteindra pas le reducer ou la requête ne sera pas envoyée. Enveloppez toujours la logique du middleware dans try-catch et enregistrez les erreurs dans Crashlytics ou Sentry sans casser la chaîne. Vérifiez régulièrement la chaîne de middleware lors des revues de code — cela évite la dégradation de l’architecture.
Questions fréquentes
Interceptor est un cas particulier de middleware pour les communications HTTP. Le middleware est un modèle plus large : il peut gérer les actions (Redux), les événements (Bloc), HTTP (Ktor) et tous les flux de données. Interceptor est toujours lié à la couche réseau et ne travaille qu’avec Request/Response.
Utilisez une méthode fabrique ou un conteneur DI (Dagger, Koin, GetIt) qui renvoie un ensemble différent de middleware pour dev et prod. Dans Redux, passez un tableau vide dans les tests. Dans Ktor, utilisez un HttpClient de test sans plugins. Le principe principal est que le middleware ne doit pas être codé en dur.
Oui — le middleware modifie l’action avant de la transmettre au reducer ou au middleware suivant. Par exemple, Redux middleware peut ajouter des métadonnées (userId, timestamp, deviceId) à chaque action sans modifier le code du dispatcher. La règle principale est de ne pas muter l’objet original mais d’en créer un nouveau via l’opérateur spread.
Dans Ktor, les termes sont interchangeables — Ktor Client middleware et plugin signifient la même chose. Chaque plugin implémente HttpClientPlugin et s’installe via client.install { }. Tous les plugins sont intégrés dans le pipeline de la requête, formant une chaîne de traitement.
En surchargeant BlocObserver.onError — un gestionnaire global appelé à chaque exception dans n’importe quel bloc. C’est une alternative au try-catch dans chaque bloc : un middleware centralisé gère les erreurs, les écrit dans Crashlytics et affiche un snackbar à l’utilisateur.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi