Logo Dealmood & Solutions
Dealmood & Solutions
Applications mobiles24 min de lecture

Kotlin Multiplatform (KMP) : architecture, fonctionnement interne et code partagé entre Android et iOS

Kotlin Multiplatform (KMP) permet de partager du code entre Android, iOS et d'autres plateformes sans imposer une architecture identique à toutes les applications. Découvrez son architecture, ses source sets, le rôle du compilateur Kotlin, le fonctionnement de Kotlin/JVM et Kotlin/Native, l'interopérabilité avec Swift et les mécanismes qui permettent au code partagé de communiquer avec les API natives.

Dealmood & Solutions

Équipe éditoriale

Kotlin Multiplatform (KMP) : architecture, fonctionnement interne et code partagé entre Android et iOS
Sommaire
  1. Kotlin Multiplatform (KMP) : architecture, fonctionnement interne et code partagé entre Android et iOS
  2. Introduction
  3. 1. Qu'est-ce que Kotlin Multiplatform ?
  4. Exemple simple
  5. 2. KMP est-il un framework d'interface graphique ?
  6. Avec KMP sans interface partagée
  7. Avec KMP et Compose Multiplatform
  8. 3. L'architecture fondamentale de Kotlin Multiplatform
  9. 3.1. Les cibles de compilation : les targets
  10. 3.2. Les source sets : organiser le code selon les plateformes
  11. 3.3. Les source sets intermédiaires
  12. 3.4. Comment KMP rassemble les source sets ?
  13. 4. Ce qui se passe sous le capot : comment le code Kotlin devient exécutable
  14. 4.1. Étape 1 : le développeur écrit le code commun
  15. 4.2. Étape 2 : Gradle configure le projet
  16. 4.3. Étape 3 : le compilateur analyse le code
  17. 4.4. Étape 4 : le code est compilé pour chaque cible
  18. Chemin Android
  19. Chemin iOS
  20. 4.5. Étape 5 : le code partagé s'exécute dans son environnement cible
  21. 5. Comment le code partagé accède-t-il aux fonctionnalités natives ?
  22. 5.1. Première approche : les interfaces et l'injection de dépendances
  23. 5.2. Deuxième approche : `expect` et `actual`
  24. 5.3. Ce que `expect` et `actual` ne font pas
  25. 6. Comment Kotlin Multiplatform communique-t-il avec Swift ?
  26. Exemple conceptuel
  27. Et si le projet utilise SwiftUI ?
  28. 7. Quelles responsabilités faut-il partager dans une architecture KMP ?
  29. La logique métier
  30. Les modèles et les cas d'utilisation
  31. L'accès réseau
  32. La persistance des données
  33. Les tests métier
  34. 8. Exemple d'architecture d'une application Android et iOS avec KMP
  35. 9. Que se passe-t-il réellement lorsqu'un utilisateur utilise l'application ?
  36. Étape 1 : l'interface récupère les données
  37. Étape 2 : l'application appelle la logique partagée
  38. Étape 3 : la couche réseau exécute la requête
  39. Étape 4 : le serveur traite la demande
  40. Étape 5 : le module partagé traite le résultat
  41. Étape 6 : l'interface affiche le résultat
  42. 10. Kotlin Multiplatform est-il plus performant qu'un framework hybride ?
  43. 11. Quels sont les avantages et les limites de KMP ?
  44. Les avantages
  45. Les limites
  46. 12. Quand faut-il choisir Kotlin Multiplatform ?
  47. Conclusion

Kotlin Multiplatform (KMP) : architecture, fonctionnement interne et code partagé entre Android et iOS

Introduction

Dans le développement mobile, maintenir une application Android et une application iOS implique souvent de faire évoluer deux bases de code distinctes. Une même règle métier peut être implémentée deux fois, les traitements peuvent progressivement diverger et les tests doivent être reproduits sur plusieurs plateformes.

C'est pour répondre à ce type de problématique que Kotlin Multiplatform, généralement abrégé KMP, prend tout son sens.

Développé par JetBrains, Kotlin Multiplatform est une technologie qui permet de partager du code entre plusieurs plateformes, notamment Android, iOS, les applications de bureau, le Web et les environnements serveur, selon les cibles et les bibliothèques utilisées.

Mais KMP ne se résume pas à écrire du Kotlin une seule fois pour l'exécuter partout.

Son intérêt architectural est plus précis : il permet de partager les traitements qui peuvent être communs tout en conservant la possibilité d'utiliser les technologies, les bibliothèques et les API propres à chaque plateforme.

Une application Android peut ainsi utiliser Kotlin/JVM et les API Android, tandis qu'une application iOS peut exploiter le même code métier compilé en code natif et l'intégrer à son environnement Swift et Apple.

Cette approche soulève plusieurs questions importantes :

  • Comment le même code Kotlin peut-il fonctionner sur des plateformes différentes ?
  • Que fait réellement le compilateur ?
  • Pourquoi le code partagé n'utilise-t-il pas librement toutes les API Android ou Java ?
  • Comment une fonction Kotlin peut-elle être appelée depuis Swift ?
  • Quel est le rôle de commonMain, androidMain et iosMain ?
  • KMP partage-t-il aussi l'interface graphique ?
  • Que se passe-t-il exactement lorsqu'on compile et lance une application ?

Pour répondre à ces questions, il faut examiner KMP depuis son architecture jusqu'à son fonctionnement interne.

1. Qu'est-ce que Kotlin Multiplatform ?

Kotlin Multiplatform est une technologie de développement multiplateforme qui permet de réutiliser du code Kotlin entre plusieurs cibles de compilation.

Le principe est simple : au lieu de réécrire systématiquement les mêmes règles métier dans chaque application, on peut les placer dans un module partagé.

Imaginons une application de commerce électronique disponible sur Android et iOS.

Les deux applications doivent calculer le prix total d'un panier, appliquer les mêmes règles de réduction, valider les données saisies et interpréter les réponses d'un serveur.

Ces traitements peuvent généralement être partagés, car leur logique ne dépend pas directement du système d'exploitation.

En revanche, certaines opérations sont spécifiques à chaque plateforme. L'ouverture de certaines interfaces système, l'intégration avec des API Apple ou la gestion de composants Android peuvent nécessiter du code distinct.

KMP permet précisément de séparer ces responsabilités.

Exemple simple

Imaginons que l'application doive calculer le prix d'un panier :

Kotlin
data class Product(
    val name: String,
    val price: Double
)

fun calculateTotal(products: List<Product>): Double {
    return products.sumOf { it.price }
}

Cette fonction est écrite en Kotlin et ne dépend pas d'une API Android ou Apple spécifique.

Elle peut donc être placée dans le code commun du projet.

Le même traitement peut être compilé pour les cibles Android et iOS déclarées dans le projet. Il n'est pas nécessaire de réécrire l'algorithme en Java ou en Swift pour obtenir le même résultat.

Attention : dans une application réelle, l'utilisation de Double pour des calculs monétaires exige des précautions. Une représentation entière en unités monétaires minimales ou une bibliothèque décimale adaptée est généralement préférable.

L'exemple illustre néanmoins le principe fondamental de KMP : partager la logique, pas nécessairement l'ensemble de l'application.

2. KMP est-il un framework d'interface graphique ?

Non. C'est une distinction essentielle.

Kotlin Multiplatform permet principalement de partager du code. Il ne dicte pas à lui seul la manière de construire l'interface graphique.

Il faut distinguer deux technologies.

Kotlin Multiplatform (KMP) permet de partager du code entre plusieurs plateformes.

Compose Multiplatform (CMP) est un framework d'interface déclarative qui permet également de partager l'interface graphique sur les plateformes prises en charge.

Ces technologies peuvent être utilisées ensemble, mais elles ne désignent pas la même chose.

Avec KMP sans interface partagée

L'application peut conserver une interface Android développée avec Jetpack Compose ou les vues Android traditionnelles, et une interface iOS développée avec SwiftUI ou UIKit.

Les deux interfaces utilisent le même module Kotlin pour les règles métier, les validations, les modèles et certains traitements techniques.

L'architecture ressemble alors à ceci :

Plain text
             APPLICATION MOBILE

       ANDROID                 iOS
    ┌─────────────┐      ┌─────────────┐
    │ Interface   │      │ Interface   │
    │ Jetpack     │      │ SwiftUI     │
    │ Compose     │      │ ou UIKit    │
    └──────┬──────┘      └──────┬──────┘
           │                    │
           └─────────┬──────────┘
                     │
             MODULE KMP PARTAGÉ
          ┌──────────────────────┐
          │ Logique métier       │
          │ Validation           │
          │ Modèles              │
          │ Cas d'utilisation    │
          │ Accès aux données    │
          └──────────────────────┘

Les interfaces restent propres à chaque système, mais les traitements communs ne sont pas dupliqués.

Avec KMP et Compose Multiplatform

Il est également possible de partager une partie importante de l'interface.

Dans ce cas, les écrans et les composants visuels sont écrits en Kotlin avec Compose Multiplatform, puis exécutés sur les plateformes prises en charge.

Cela peut réduire davantage la duplication du code d'interface, mais implique d'autres considérations concernant l'intégration, le rendu, l'accessibilité et l'expérience utilisateur.

Le choix dépend donc du projet.

KMP permet de partager la logique. Compose Multiplatform permet aussi de partager l'interface. Il est possible d'adopter l'un sans nécessairement adopter l'autre.

3. L'architecture fondamentale de Kotlin Multiplatform

Pour comprendre KMP, il faut examiner les différents niveaux qui composent un projet.

Son architecture repose notamment sur les cibles de compilation, les source sets, les dépendances multiplateformes et les implémentations propres à chaque plateforme.

3.1. Les cibles de compilation : les targets

Une cible, ou target, indique au système de compilation la plateforme pour laquelle le code doit être préparé.

Selon le projet et les versions des outils utilisés, on peut notamment déclarer des cibles Android/JVM, iOS, JVM, JavaScript, WebAssembly ou certaines plateformes de bureau.

Chaque cible détermine notamment :

  • Le format du résultat compilé.
  • Les bibliothèques et API disponibles.
  • Les contraintes de compatibilité.
  • Les opérations de compilation nécessaires.
  • Les possibilités d'interopérabilité avec la plateforme.

Prenons un projet qui cible Android et iOS.

Le développeur déclare les cibles correspondantes dans la configuration Kotlin Multiplatform.

Le code commun sera ensuite intégré à la compilation de chaque cible.

Cela ne signifie pas que le compilateur produit un fichier binaire unique compatible avec Android et iOS. Il produit des résultats adaptés aux cibles déclarées.

Le code source peut être partagé, mais les artefacts compilés diffèrent selon la plateforme.

3.2. Les source sets : organiser le code selon les plateformes

Un source set est un ensemble de fichiers source associé à des cibles de compilation et à des dépendances.

C'est l'un des concepts les plus importants de KMP.

Un projet mobile peut notamment contenir les source sets suivants :

Plain text
shared/
└── src/
    ├── commonMain/
    ├── commonTest/
    ├── androidMain/
    ├── androidUnitTest/
    ├── iosMain/
    └── iosTest/

Chaque répertoire possède un rôle particulier.

`commonMain`

Il contient le code destiné à être partagé entre toutes les cibles compatibles.

On y place généralement les modèles, les règles métier, les cas d'utilisation, les interfaces de services et les traitements qui ne dépendent pas d'une API spécifique à une plateforme.

`commonTest`

Il contient les tests communs qui peuvent être exécutés sur les cibles concernées.

Il permet notamment de vérifier les règles métier et les comportements partagés.

`androidMain`

Il contient le code spécifique à Android.

On peut notamment y utiliser les API Android et les dépendances JVM compatibles avec la configuration du projet.

`iosMain`

Il contient le code spécifique aux plateformes iOS ciblées par le projet.

Il permet notamment d'accéder aux API Apple disponibles à travers Kotlin/Native.

Les source sets de tests spécifiques

Ils permettent d'écrire des tests qui dépendent des outils ou des API propres à chaque plateforme.

La séparation entre code commun et code spécifique n'est pas une simple convention de rangement. Elle influence directement les API utilisables et les dépendances disponibles lors de la compilation.

3.3. Les source sets intermédiaires

Dans un projet plus complexe, le partage ne concerne pas nécessairement toutes les plateformes.

Imaginons qu'un traitement soit commun à iOS, macOS et watchOS, mais qu'il utilise des API disponibles uniquement dans l'écosystème Apple.

Il serait peu pratique de dupliquer ce traitement dans chaque source set.

KMP permet alors d'utiliser un source set intermédiaire, par exemple appleMain, pour regrouper le code partagé entre les cibles Apple concernées.

La hiérarchie devient :

Plain text
commonMain
    │
    ├── androidMain
    │
    └── appleMain
          ├── iosMain
          ├── macosMain
          └── watchosMain

Ce schéma est illustratif : la hiérarchie réelle dépend des cibles et des source sets configurés.

L'intérêt est de partager le code au niveau approprié, sans imposer artificiellement un code identique à toutes les plateformes.

3.4. Comment KMP rassemble les source sets ?

Lorsqu'une cible est compilée, le système de compilation sélectionne les source sets qui contribuent à cette cible, en respectant leur hiérarchie et leurs dépendances.

Pour une compilation iOS, le code commun, le code des source sets intermédiaires compatibles et le code spécifique à la cible sont pris en compte.

Pour Android, le code commun et les sources destinées à la cible Android sont sélectionnés.

Les dépendances doivent également être compatibles avec la cible concernée.

Ainsi, le compilateur peut vérifier qu'un fichier placé dans commonMain ne dépend pas directement d'une classe qui n'existe que dans l'environnement Android.

Cette organisation rend possible le partage du code tout en empêchant certaines dépendances incompatibles d'entrer dans le code commun.

4. Ce qui se passe sous le capot : comment le code Kotlin devient exécutable

C'est ici que KMP devient particulièrement intéressant.

Un développeur peut écrire une fonction Kotlin dans un module partagé, mais cette fonction ne s'exécute pas nécessairement de la même manière sur Android et iOS.

La différence vient du processus de compilation et de l'environnement d'exécution de chaque cible.

4.1. Étape 1 : le développeur écrit le code commun

Imaginons que le module partagé contienne cette fonction :

Kotlin
fun calculateDiscount(
    price: Double,
    percentage: Double
): Double {
    return price * percentage / 100.0
}

Cette fonction ne dépend d'aucune API propre à Android ou à iOS.

Elle peut donc être compilée pour plusieurs cibles compatibles.

Le développeur écrit la règle une seule fois.

4.2. Étape 2 : Gradle configure le projet

Dans un projet KMP habituel, Gradle orchestre le processus de construction.

La configuration du projet indique notamment :

  • Les plugins utilisés.
  • Les cibles de compilation.
  • Les source sets.
  • Les dépendances.
  • Les options de compilation.
  • Les tâches de construction et de test.

Lorsqu'on demande de construire l'application Android ou le framework iOS, Gradle exécute les tâches nécessaires pour préparer les artefacts correspondants.

Il ne transforme pas lui-même le code Kotlin en instructions machine. Il coordonne les outils de compilation et les étapes de construction.

4.3. Étape 3 : le compilateur analyse le code

Le compilateur Kotlin analyse les fichiers sources, vérifie la syntaxe et les types, puis prépare une représentation interne du programme pour les étapes suivantes.

Il vérifie également les contraintes liées aux déclarations multiplateformes et aux API accessibles depuis chaque source set.

Par exemple, ce code n'est pas adapté à commonMain :

Kotlin
import java.io.File

fun readFile(): String {
    return File("message.txt").readText()
}

La raison est simple : java.io.File appartient à l'environnement Java/JVM. Il n'est pas disponible sur toutes les cibles de Kotlin Multiplatform, notamment les cibles natives iOS.

Si cette opération doit être commune, il faut utiliser une abstraction ou une bibliothèque multiplateforme adaptée. Si elle est propre à une plateforme, elle doit être implémentée dans le source set approprié.

Cette vérification constitue l'un des mécanismes fondamentaux de KMP.

4.4. Étape 4 : le code est compilé pour chaque cible

Le compilateur utilise un backend adapté à la cible déclarée.

Pour Android, le code Kotlin est généralement compilé en bytecode JVM, puis intégré au processus de construction Android.

Pour iOS, Kotlin/Native compile le code Kotlin vers des binaires natifs, en utilisant une chaîne de compilation fondée sur LLVM.

Les deux chemins sont différents.

Chemin Android

Plain text
Code Kotlin partagé
        ↓
Compilation Kotlin/JVM
        ↓
Bytecode JVM (.class)
        ↓
Construction de l'application Android
        ↓
DEX / APK ou App Bundle
        ↓
ART sur l'appareil Android

Ce schéma représente le chemin général d'une application Android utilisant Kotlin/JVM.

Le bytecode JVM produit par Kotlin est transformé et intégré au format exécutable Android. Sur l'appareil, Android Runtime (ART) exécute le code selon les mécanismes de la plateforme.

Il ne faut donc pas confondre le bytecode JVM avec le format DEX utilisé dans les applications Android.

Chemin iOS

Plain text
Code Kotlin partagé
        ↓
Compilateur Kotlin
        ↓
Backend Kotlin/Native
        ↓
Chaîne de compilation LLVM
        ↓
Binaire natif / framework
        ↓
Intégration dans l'application iOS
        ↓
Exécution sur iPhone ou iPad

Pour iOS, le code Kotlin est compilé en code natif adapté à la cible Apple.

Il ne nécessite pas une machine virtuelle JVM pour exécuter le code Kotlin partagé.

Le résultat peut notamment être distribué sous forme de framework natif intégré au projet iOS.

Il faut retenir que les artefacts et les étapes exactes dépendent de la cible, de la configuration et de la chaîne de construction utilisée.

4.5. Étape 5 : le code partagé s'exécute dans son environnement cible

Sur Android, le code partagé fonctionne dans l'environnement Android/JVM.

Sur iOS, il s'exécute sous la forme de code natif produit par Kotlin/Native.

Dans les deux cas, la logique métier peut être identique, mais son intégration technique n'est pas la même.

C'est précisément ce qui permet à KMP de concilier réutilisation du code et intégration aux plateformes.

KMP ne fait pas simplement tourner un programme universel dans une couche commune : il adapte le code partagé aux cibles de compilation déclarées.

5. Comment le code partagé accède-t-il aux fonctionnalités natives ?

Le code commun ne peut pas utiliser directement toutes les API disponibles sur Android ou iOS.

Prenons un besoin simple : récupérer une information spécifique à l'appareil.

Sur Android, cette information peut être obtenue avec une API Android. Sur iOS, elle peut nécessiter une API Apple.

KMP propose plusieurs moyens d'organiser cette différence.

5.1. Première approche : les interfaces et l'injection de dépendances

Il s'agit souvent de l'approche la plus souple.

Le code commun définit une interface :

Kotlin
interface DeviceInfoProvider {
    fun getDeviceName(): String
}

Le code commun peut ensuite utiliser cette interface sans connaître l'implémentation technique.

Chaque plateforme fournit sa propre implémentation.

Par exemple, Android peut exploiter certaines API Android, tandis qu'iOS peut utiliser les API Apple appropriées.

Une implémentation est ensuite fournie au code qui en a besoin, par exemple au moyen de l'injection de dépendances.

Cette approche facilite également les tests, puisqu'une implémentation fictive peut être fournie sans accéder à un appareil réel.

5.2. Deuxième approche : `expect` et `actual`

Kotlin Multiplatform possède aussi un mécanisme permettant de déclarer une fonctionnalité attendue dans le code commun et de fournir une implémentation spécifique pour les cibles concernées.

Ce mécanisme repose sur les mots-clés expect et actual.

Dans commonMain :

Kotlin
expect fun getPlatformName(): String

Cette déclaration indique qu'une implémentation adaptée doit être disponible pour les cibles qui utilisent cette fonction.

Dans androidMain :

Kotlin
actual fun getPlatformName(): String {
    return "Android"
}

Dans iosMain :

Kotlin
actual fun getPlatformName(): String {
    return "iOS"
}

Le code commun peut ensuite appeler la fonction :

Kotlin
fun greeting(): String {
    return "Bonjour depuis ${getPlatformName()}"
}

Le code métier utilise une seule fonction, mais l'implémentation sélectionnée dépend de la cible compilée.

Il ne s'agit pas d'une fonction magique qui détecte arbitrairement le système à chaque appel. La correspondance entre les déclarations attendues et les implémentations est établie dans le cadre de la compilation multiplateforme.

Cette approche est utile pour certaines différences de plateforme. Elle ne doit cependant pas être employée systématiquement : une interface classique suffit souvent et offre davantage de souplesse lorsqu'il faut gérer plusieurs implémentations.

5.3. Ce que `expect` et `actual` ne font pas

Ces mécanismes ne rendent pas automatiquement toutes les API Android ou Apple disponibles dans commonMain.

Si une application doit utiliser la caméra, le Bluetooth ou une API système particulière, elle doit toujours disposer d'une implémentation adaptée, d'une bibliothèque compatible ou d'un mécanisme d'interopérabilité approprié.

expect et actual permettent d'organiser le contrat commun et ses implémentations. Ils ne remplacent ni les API des systèmes ni les autorisations nécessaires.

6. Comment Kotlin Multiplatform communique-t-il avec Swift ?

Cette question est particulièrement importante lorsqu'on développe une application iOS.

Kotlin et Swift sont deux langages différents, associés à des environnements de compilation distincts.

Pour utiliser le code Kotlin partagé dans une application iOS, le projet peut produire un framework natif exposant les déclarations Kotlin nécessaires.

Le projet iOS peut alors intégrer ce framework et appeler les éléments qu'il expose.

Le processus général est le suivant :

  1. Le développeur écrit le code partagé en Kotlin.
  2. Kotlin/Native compile ce code pour la cible Apple.
  3. Le processus de construction produit un framework ou un autre artefact natif adapté à l'intégration.
  4. Le projet iOS référence cet artefact.
  5. Swift appelle les API exposées par le framework.

L'interopérabilité repose sur les mécanismes natifs prévus par l'écosystème Apple, notamment les interfaces Objective-C utilisées pour exposer le framework Kotlin aux projets Swift.

Il ne faut toutefois pas supposer que chaque déclaration Kotlin devient automatiquement une API Swift parfaitement idiomatique. La visibilité des déclarations, les types exposés et la représentation des API doivent être pris en compte.

Exemple conceptuel

Le module partagé définit :

Kotlin
class PriceCalculator {
    fun calculate(price: Double, tax: Double): Double {
        return price * (1.0 + tax / 100.0)
    }
}

Une fois le module compilé et intégré au projet iOS, Swift peut utiliser le type exposé par le framework pour effectuer le calcul.

L'intérêt est que la règle de calcul est maintenue en Kotlin, alors que l'interface et le reste de l'application iOS peuvent rester écrits en Swift.

Le nom exact des symboles accessibles depuis Swift dépend de la configuration du framework et des règles d'interopérabilité.

Et si le projet utilise SwiftUI ?

C'est tout à fait possible.

SwiftUI peut rester responsable de l'interface. Le code Swift appelle ensuite le module partagé pour effectuer les traitements métier.

Par exemple, un écran SwiftUI peut récupérer les produits saisis par l'utilisateur, appeler le calculateur Kotlin et afficher le total obtenu.

L'interface reste native à l'écosystème Apple, tandis que la logique de calcul est mutualisée.

C'est l'un des modèles d'architecture les plus intéressants de KMP : partager le code qui gagne à l'être, sans imposer une interface identique sur toutes les plateformes.

7. Quelles responsabilités faut-il partager dans une architecture KMP ?

Une bonne architecture KMP ne consiste pas à déplacer tous les fichiers dans un module commun.

Il faut déterminer quelles responsabilités peuvent réellement être mutualisées.

Pour une application mobile, les éléments suivants sont souvent de bons candidats.

La logique métier

Les règles de calcul, les validations, les politiques tarifaires et les règles de gestion peuvent être partagées lorsqu'elles ne dépendent pas d'une plateforme.

Cela permet d'éviter les divergences entre les versions Android et iOS.

Les modèles et les cas d'utilisation

Les objets métier, les résultats des opérations et les cas d'utilisation peuvent être communs.

Une application de commerce peut, par exemple, partager les règles qui déterminent si un panier peut être validé.

L'accès réseau

Les requêtes HTTP, les modèles de données et certaines opérations de sérialisation peuvent être partagés à l'aide de bibliothèques compatibles avec les cibles concernées.

Ktor est un exemple de bibliothèque multiplateforme permettant de construire des clients réseau.

Le code commun ne doit toutefois pas être considéré comme automatiquement compatible avec n'importe quelle bibliothèque Java. Les dépendances doivent prendre en charge les cibles auxquelles le module est destiné.

La persistance des données

Il est également possible de partager une partie de l'accès aux données locales avec des bibliothèques multiplateformes adaptées.

La compatibilité des bibliothèques, les différences de stockage et les contraintes de chaque système doivent cependant être évaluées.

Les tests métier

Les tests des règles communes peuvent être écrits une seule fois et exécutés sur les cibles pertinentes.

Cela permet de détecter plus facilement les divergences et de renforcer la fiabilité du code partagé.

En revanche, les tests d'intégration avec la caméra, les notifications ou les API système doivent tenir compte des comportements propres à chaque plateforme.

8. Exemple d'architecture d'une application Android et iOS avec KMP

Imaginons que nous développions une application de messagerie.

L'objectif est de partager les règles métier et les échanges réseau tout en conservant des interfaces adaptées à Android et iOS.

Une architecture possible serait :

Plain text
                    APPLICATION MOBILE
                            │
              ┌─────────────┴─────────────┐
              │                           │
          ANDROID                       iOS
              │                           │
       Jetpack Compose                 SwiftUI
              │                           │
              └─────────────┬─────────────┘
                            │
                    MODULE KMP PARTAGÉ
                            │
              ┌─────────────┴─────────────┐
              │                           │
         Logique métier              Couche données
              │                           │
       Cas d'utilisation            Client HTTP
       Validation                   Sérialisation
       Modèles                      Repository
              │                           │
              └─────────────┬─────────────┘
                            │
                      API distante

Dans cette architecture :

  • Android conserve son interface et son intégration propres à Android.
  • iOS conserve son interface et son intégration propres à Apple.
  • Le module KMP centralise les règles communes.
  • La couche réseau mutualise les opérations compatibles.
  • Le serveur conserve la responsabilité des données et des traitements qui lui appartiennent.

Il faut noter qu'un tel schéma est une possibilité architecturale, et non une structure imposée par KMP.

Un projet peut partager davantage de composants, notamment l'interface graphique avec Compose Multiplatform, ou au contraire limiter le module commun à quelques règles métier.

L'important est de définir clairement les responsabilités de chaque couche.

9. Que se passe-t-il réellement lorsqu'un utilisateur utilise l'application ?

Pour comprendre le fonctionnement au-delà de la compilation, prenons un exemple de connexion à une application.

L'utilisateur saisit son adresse e-mail et son mot de passe, puis appuie sur le bouton de connexion.

Étape 1 : l'interface récupère les données

Sur Android, l'écran peut être développé avec Jetpack Compose.

Sur iOS, il peut être développé avec SwiftUI.

Chaque interface collecte les informations saisies et déclenche l'action de connexion.

Étape 2 : l'application appelle la logique partagée

L'interface transmet les données au composant chargé de la connexion.

Ce composant peut être écrit en Kotlin dans le module KMP.

Il applique les validations communes, prépare l'opération et coordonne l'accès aux données.

Étape 3 : la couche réseau exécute la requête

Si le module partagé utilise une bibliothèque multiplateforme comme Ktor, il peut envoyer une requête HTTP vers le serveur.

Le moteur réseau utilisé dépend de la plateforme et de la configuration du client.

Le code métier peut rester commun même si les mécanismes techniques utilisés sous le capot diffèrent.

Étape 4 : le serveur traite la demande

Le serveur vérifie les informations selon ses règles de sécurité et renvoie un résultat.

La validation côté client améliore l'expérience utilisateur, mais elle ne remplace jamais la validation côté serveur.

Étape 5 : le module partagé traite le résultat

Le code Kotlin interprète la réponse, transforme les données et expose un résultat à l'application.

Les règles communes peuvent ainsi rester cohérentes entre Android et iOS.

Étape 6 : l'interface affiche le résultat

Android et iOS présentent le résultat avec leurs composants respectifs.

L'utilisateur peut donc bénéficier d'une expérience adaptée à chaque plateforme, même si une partie du traitement a été écrite une seule fois.

Cet exemple illustre la valeur de KMP : partager les traitements sans obliger toutes les couches à être identiques.

10. Kotlin Multiplatform est-il plus performant qu'un framework hybride ?

Il faut distinguer plusieurs aspects : le temps d'exécution, l'accès au système, le rendu de l'interface et la vitesse de développement.

KMP permet de compiler le code partagé selon les mécanismes de la plateforme cible. Sur Android, le code Kotlin/JVM s'intègre à l'environnement Android. Sur iOS, Kotlin/Native produit du code natif.

Cette architecture permet d'éviter l'obligation d'exécuter toute la logique partagée dans une machine virtuelle Java sur iOS.

Cependant, cela ne signifie pas que KMP est automatiquement plus rapide que tous les frameworks hybrides ou multiplateformes.

Les performances dépendent notamment :

  • De l'algorithme utilisé.
  • De la gestion de la mémoire.
  • Des accès réseau et disque.
  • Des bibliothèques utilisées.
  • De l'architecture de l'application.
  • De la manière dont l'interface est rendue.
  • Des caractéristiques des appareils ciblés.

Il faut aussi distinguer KMP d'un framework qui fournit une interface graphique multiplateforme. Lorsque Compose Multiplatform est utilisé, le rendu et l'intégration de l'interface constituent une question distincte de la compilation du code métier.

Le principal avantage architectural de KMP est donc la possibilité de partager du code tout en conservant une intégration adaptée aux plateformes. Les performances doivent être mesurées sur les opérations réelles du projet.

11. Quels sont les avantages et les limites de KMP ?

Les avantages

Réduction de la duplication du code

Les règles métier et les traitements communs ne doivent pas nécessairement être réécrits pour chaque plateforme.

Cohérence fonctionnelle

Les applications Android et iOS peuvent utiliser les mêmes règles de validation et les mêmes algorithmes métier.

Conservation des technologies natives

Les équipes peuvent garder Jetpack Compose ou les vues Android sur Android, et SwiftUI ou UIKit sur iOS.

Intégration progressive

Il est possible d'introduire KMP dans un projet existant en commençant par un module limité, sans réécrire immédiatement toute l'application.

Tests communs

Les règles métier partagées peuvent être testées de manière cohérente sur les cibles concernées.

Flexibilité architecturale

Le projet peut partager uniquement les éléments qui apportent une réelle valeur, sans imposer une base de code totalement commune.

Les limites

Complexité de la construction

La configuration Gradle, les cibles, les dépendances et les chaînes de compilation peuvent rendre le projet plus complexe qu'une application limitée à une seule plateforme.

Interopérabilité avec iOS

L'intégration entre Kotlin/Native et Swift nécessite de comprendre les règles d'exposition des API et les contraintes de l'écosystème Apple.

Disponibilité des bibliothèques

Toutes les bibliothèques Kotlin ou Java ne sont pas compatibles avec toutes les cibles KMP.

Gestion des spécificités système

Certaines fonctionnalités nécessitent toujours du code propre à Android ou à iOS.

Partage de l'interface non automatique

KMP ne signifie pas que les écrans sont automatiquement partagés. Pour cela, il faut adopter une solution d'interface multiplateforme comme Compose Multiplatform.

Compromis architecturaux

Partager du code n'est pas toujours avantageux. Une interface, une fonctionnalité ou un composant fortement dépendant du système peut être plus simple à maintenir lorsqu'il reste spécifique à sa plateforme.

12. Quand faut-il choisir Kotlin Multiplatform ?

KMP est particulièrement intéressant lorsqu'une équipe souhaite partager une partie de la logique entre Android et iOS tout en conservant une maîtrise des technologies propres à chaque plateforme.

Il convient notamment aux projets qui disposent déjà d'applications natives et souhaitent réduire la duplication des traitements métier.

Il peut aussi être adapté à une nouvelle application lorsque les équipes veulent centraliser les règles métier, l'accès réseau et certains composants techniques.

En revanche, si le principal objectif consiste à développer une interface unique pour plusieurs plateformes, il faut également évaluer Compose Multiplatform et les autres frameworks d'interface multiplateforme.

Le choix dépend donc du niveau de partage souhaité.

Une équipe peut décider de partager uniquement les modèles et les règles métier, puis d'étendre progressivement KMP à l'accès réseau, à la persistance et à d'autres composants.

Cette progression permet de mesurer les bénéfices réels avant d'augmenter la complexité du projet.

Conclusion

Kotlin Multiplatform est bien plus qu'un moyen d'écrire du code Kotlin réutilisable. C'est une technologie qui permet d'organiser le partage du code selon les plateformes, de compiler les composants communs pour des environnements différents et de conserver l'accès aux API propres à chaque système.

Son architecture repose notamment sur les cibles de compilation, les source sets, les dépendances compatibles, les mécanismes d'interopérabilité et, lorsque cela est nécessaire, des implémentations spécifiques.

Sous le capot, le code partagé suit un chemin de compilation adapté à chaque plateforme : Kotlin/JVM pour l'environnement Android/JVM et Kotlin/Native pour les cibles natives telles qu'iOS.

Le code commun ne devient donc pas un binaire universel. Il est intégré à des artefacts adaptés aux plateformes ciblées.

C'est cette approche qui permet de combiner réutilisation du code, flexibilité architecturale et intégration native.

En définitive, la force de Kotlin Multiplatform ne consiste pas à supprimer toutes les différences entre Android et iOS. Elle consiste à partager intelligemment ce qui peut l'être, tout en conservant la liberté d'exploiter les spécificités de chaque plateforme.

Commentaires (0)

Vous devez être connecté pour laisser un commentaire.

Se connecter

Aucun commentaire pour le moment. Soyez le premier à réagir !