Kotlin Multiplatform 2026 : partager du code Android/iOS
Comment Kotlin Multiplatform partage la logique métier entre Android et iOS en 2026 : configuration Gradle, expect/actual, réseau Ktor, interface Compose Multiplatform et questions d'entretien KMP courantes.

Kotlin Multiplatform (KMP) permet à une seule base de code Kotlin d'alimenter la logique métier des applications Android et iOS, tout en laissant chaque plateforme conserver son interface entièrement native, ses performances et l'accès à son SDK. En 2026, KMP n'est plus une expérimentation : la technologie est stable, officiellement soutenue par Google et déployée en production chez Netflix, McDonald's, Forbes et Cash App. Ce dossier détaille le fonctionnement réel du partage de code, la configuration d'un module partagé et les compromis à comprendre avant un entretien KMP.
Kotlin Multiplatform partage de la logique compilée, pas un runtime. Le Kotlin commun compile en bytecode JVM pour Android et en binaire natif (via LLVM) pour iOS. Il n'y a ni pont JavaScript, ni webview embarquée, ni couche de réflexion : le code partagé s'exécute donc à vitesse native sur les deux plateformes.
Comment Kotlin Multiplatform partage le code sans sacrifier les performances natives
Le mécanisme central repose sur une hiérarchie de source sets. commonMain contient le Kotlin indépendant de la plateforme : réseau, sérialisation, règles métier et view models. Les source sets de plateforme comme androidMain et iosMain regroupent le code qui touche aux API natives. Le compilateur produit un artefact JVM à partir de commonMain et androidMain pour Android, et un framework natif à partir de commonMain et iosMain pour iOS.
En 2026, Kotlin Multiplatform privilégie le partage de ce qui bénéficie réellement d'une source de vérité unique : modèles, clients d'API, cache et validation. L'interface peut être partagée avec Compose Multiplatform ou rester entièrement native avec SwiftUI, écran par écran. Ce principe (« partager la logique, choisir l'interface ») explique pourquoi les équipes adoptent KMP sans réécrire leurs applications existantes.
Kotlin Multiplatform est une technologie Kotlin qui compile un module partagé vers plusieurs cibles (Android sur la JVM, iOS en natif, ainsi que le bureau et le web) : la logique métier est écrite une seule fois pendant que chaque plateforme conserve son interface native et l'accès complet à son SDK. Google la prend désormais en charge comme une option de premier plan, et des bibliothèques Jetpack comme Room, DataStore, ViewModel et Paging proposent des artefacts KMP.
Décider de ce qui appartient au module partagé est la décision d'architecture la plus importante d'un projet KMP. Le tableau ci-dessous reflète la façon dont la plupart des équipes en production tracent la ligne en 2026 : tout ce qui se situe sous la couche de présentation est un bon candidat au partage, tandis que tout ce qui touche aux conventions UX de la plateforme reste généralement natif.
| Couche | Partager dans commonMain ? | Raison | |-------|---------------------|--------| | Modèles de données et DTO | Oui | Contrat d'API identique sur les deux plateformes | | Réseau et cache | Oui | Une seule pile HTTP, une seule surface de bugs | | Règles métier et validation | Oui | Le domaine ne diffère pas selon l'OS | | View models et état | Généralement | Compose et SwiftUI consomment tous deux l'état partagé | | Rendu de l'interface | Optionnel | Partagé avec Compose, ou natif écran par écran | | API de plateforme (caméra, biométrie) | Non | À exposer plutôt via expect/actual |
Le gain augmente avec la part de la moitié inférieure de ce tableau que couvre une application. Les produits riches en données dotés d'interfaces légères partagent le plus ; les applications très sur mesure, portées par l'animation, partagent le moins. Aucun de ces extrêmes n'est mauvais, ce qui explique pourquoi KMP convient à une adoption progressive plutôt qu'à une réécriture radicale.
Configurer le module partagé dans Gradle
Un module KMP déclare ses cibles et ses source sets en DSL Kotlin. Le fichier build.gradle.kts suivant configure Android et les trois architectures iOS (appareil physique, simulateur Intel, simulateur Apple Silicon) et expose la sortie iOS sous la forme d'un framework nommé Shared.
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
id("com.android.library")
}
kotlin {
// Android target compiles commonMain + androidMain to JVM bytecode
androidTarget()
// Three iOS targets: device, Intel sim, Apple Silicon sim
listOf(iosArm64(), iosX64(), iosSimulatorArm64()).forEach { target ->
target.binaries.framework {
baseName = "Shared" // becomes "import Shared" in Swift
isStatic = true
}
}
sourceSets {
commonMain.dependencies {
implementation("io.ktor:ktor-client-core:3.3.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
}
androidMain.dependencies {
implementation("io.ktor:ktor-client-okhttp:3.3.0")
}
iosMain.dependencies {
implementation("io.ktor:ktor-client-darwin:3.3.0")
}
}
}Chaque cible tire un moteur Ktor spécifique à la plateforme (okhttp sur Android, darwin sur iOS) tandis que le code partagé ne dépend que de l'API commune ktor-client-core. L'ensemble des options est décrit dans la documentation de Kotlin Multiplatform.
Écrire du code spécifique à la plateforme avec expect et actual
Certaine logique ne peut pas résider dans commonMain car elle nécessite une API de plateforme. Le mécanisme expect/actual déclare la forme dans le code commun et fournit une implémentation concrète par cible, résolue à la compilation sans aucun coût à l'exécution.
// commonMain declares what it needs but cannot implement here
expect class Platform() {
val name: String
val osVersion: String
}
// Shared logic can now depend on Platform freely
fun analyticsUserAgent(): String {
val platform = Platform()
return "SharpSkill/" + platform.name + " " + platform.osVersion
}L'implémentation Android lit depuis la classe Build, et l'implémentation iOS lit depuis UIDevice. Toutes deux respectent le même contrat, si bien que commonMain ne sait jamais à laquelle elle s'adresse.
import android.os.Build
// actual supplies the Android-specific implementation
actual class Platform actual constructor() {
actual val name: String = "Android"
actual val osVersion: String = Build.VERSION.RELEASE
}import platform.UIKit.UIDevice
// actual uses UIKit directly, no Objective-C bridging code required
actual class Platform actual constructor() {
actual val name: String = "iOS"
actual val osVersion: String = UIDevice.currentDevice.systemVersion
}Kotlin/Native fournit des liaisons typées pour l'intégralité du SDK iOS : UIDevice, NSUserDefaults et les types Foundation sont donc appelables depuis Kotlin sans code de liaison écrit à la main.
Partager la logique réseau et de sérialisation
Le réseau est le code le plus rentable à partager, car les contrats d'API sont identiques sur les deux plateformes. Un seul client Ktor associé à kotlinx.serialization remplace deux implémentations parallèles. Les fonctions suspend s'appuient sur les coroutines Kotlin, qui se traduisent proprement en async/await des deux côtés.
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.Serializable
// One serializable model, zero duplication across platforms
@Serializable
data class Question(val id: String, val prompt: String, val difficulty: String)
// One HttpClient, one JSON parser, one repository for both apps
class InterviewRepository {
private val client = HttpClient {
install(ContentNegotiation) { json() }
}
suspend fun fetchQuestions(tech: String): List<Question> =
client.get("https://api.sharpskill.dev/questions/" + tech).body()
}Ce dépôt, sa logique de reprise et ses modèles de données sont écrits une seule fois. Un bug corrigé dans le parseur l'est simultanément sur Android et iOS, et c'est là que KMP rentabilise son coût de mise en place. Le même schéma s'étend à une base de données partagée avec SQLDelight, à un stockage clé-valeur partagé et à une injection de dépendances partagée avec Koin : toute la couche de données peut ainsi résider dans commonMain derrière des interfaces dont dépend l'interface utilisateur.
Une erreur courante consiste à vouloir partager trop de choses trop tôt. La voie pragmatique consiste à commencer par un seul dépôt, à valider le build et le pipeline CI sur les deux plateformes, puis à migrer les fonctionnalités une par une à mesure que la confiance grandit.
Prêt à réussir tes entretiens Android ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Construire une interface partagée avec Compose Multiplatform
Compose Multiplatform a atteint le statut stable sur iOS en mai 2025 (version 1.8.0) et la version 1.11.0 à la mi-2026, avec la saisie de texte native et le rendu concurrent activé par défaut. Un @Composable dans commonMain s'affiche sur les deux plateformes, et les view models partagés s'accordent naturellement avec une architecture MVVM ou MVI.
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.padding
// This list renders identically on Android and iOS
@Composable
fun QuestionList(questions: List<Question>) {
LazyColumn {
items(questions) { question ->
Card(modifier = Modifier.padding(8.dp)) {
Text(text = question.prompt, modifier = Modifier.padding(16.dp))
}
}
}
}Le partage de l'interface est optionnel et se fait écran par écran. Une équipe peut partager toute l'interface avec Compose Multiplatform, conserver SwiftUI pour l'application entière et ne partager que la logique, ou combiner les deux. Les écrans qui dépendent d'une UX spécifique à la plateforme, comme les widgets ou les App Clips, restent généralement natifs.
Consommer le code partagé depuis Swift sur iOS
Le module partagé compile vers un framework que Swift importe comme n'importe quelle bibliothèque. La friction venait historiquement des fonctions suspend de Kotlin et de Flow, exposés sous forme d'API à callbacks peu pratiques. SKIE, de Touchlab, résout ce problème en générant du Swift idiomatique : suspend devient un async/await natif et Flow devient un AsyncSequence.
import SwiftUI
import Shared // the KMP framework, baseName = "Shared"
struct ContentView: View {
@State private var questions: [Question] = []
private let repository = InterviewRepository()
var body: some View {
List(questions, id: \.id) { question in
Text(question.prompt)
}
.task {
// With SKIE, the Kotlin suspend fun is a Swift async fun
questions = (try? await repository.fetchQuestions(tech: "android")) ?? []
}
}
}L'équipe iOS écrit du Swift et du SwiftUI ordinaires. Elle consomme les modèles et dépôts partagés exactement comme un package Swift natif, ce qui rend KMP invisible pour la majeure partie du code iOS.
Deux aspérités méritent encore d'être mentionnées en entretien. D'abord les temps de build : la compilation Kotlin/Native est plus lente qu'un build Swift pur, même si le cache de build Gradle et la distribution d'un XCFramework précompilé atténuent le coût quotidien. Ensuite, le débogage à la frontière Swift-Kotlin s'améliore mais n'est pas encore aussi fluide que de rester dans un seul langage, une raison de plus pour les équipes de garder une surface partagée délibérée plutôt que tentaculaire.
Questions d'entretien Kotlin Multiplatform à prévoir
Comme les questions d'entretien sur KMP apparaissent de plus en plus dans les postes Android et mobiles, quelques classiques méritent d'être révisés en parallèle d'une préparation aux entretiens Android plus large :
- En quoi expect/actual diffère-t-il d'une interface ? expect/actual se résout à la compilation pour chaque cible, sans dispatch à l'exécution, et peut soutenir une classe, une fonction, une propriété ou un typealias. Une interface se résout dynamiquement à l'exécution. On utilise expect/actual pour les API de plateforme et les interfaces pour le polymorphisme au sein du code partagé.
- Comment Kotlin/Native gère-t-il la mémoire et les threads ? Depuis Kotlin 1.7.20, le nouveau gestionnaire de mémoire a supprimé l'ancien modèle de gel des objets, si bien que l'état mutable partagé et les coroutines fonctionnent entre threads presque comme sur la JVM.
- Une application existante peut-elle adopter KMP de façon progressive ? Oui. Le module partagé est livré sous forme d'AAR pour Android et de XCFramework pour iOS : un seul dépôt ou une seule fonctionnalité peut donc migrer à la fois, sans réécriture.
- Quand l'interface doit-elle rester native plutôt que d'utiliser Compose Multiplatform ? Lorsqu'un écran s'appuie sur une UX spécifique à la plateforme ou lorsque l'équipe iOS possède et préfère contrôler la couche de présentation.
Viser Kotlin 2.3 avec le compilateur K2, Ktor 3.x pour le réseau, SQLDelight pour une base de données partagée et Koin pour l'injection de dépendances. Pour l'interopérabilité iOS, ajouter SKIE tôt : l'intégrer après coup, une fois la couche Swift écrite, est bien plus pénible.
Conclusion
En 2026, Kotlin Multiplatform est un moyen pragmatique de réduire la logique dupliquée entre Android et iOS sans renoncer à l'interface ni aux performances natives :
- Partager la logique qui possède une source de vérité unique (modèles, réseau, validation) et choisir une interface native ou Compose écran par écran.
- Utiliser
expect/actualpour les API de plateforme : la résolution se fait à la compilation, sans surcoût à l'exécution. - Écrire le réseau une seule fois avec Ktor et
kotlinx.serializationpour que les corrections atterrissent sur les deux plateformes en même temps. - Adopter la technologie progressivement via un AAR et un XCFramework plutôt que de s'engager dans une réécriture.
- Ajouter SKIE dès le départ pour que Swift consomme
suspendetFlowsous forme deasync/awaitetAsyncSequencenatifs.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tags
Partager
Articles similaires

Kotlin 2.3 pour Android : Destructuration par Nom, KMP et Questions d'Entretien 2026
Questions d'entretien Kotlin 2.3 pour développeurs Android en 2026. Destructuration par nom, KMP, paramètres de contexte, Flow et coroutines avec exemples de code.

Kotlin Flow vs StateFlow vs SharedFlow : questions d'entretien Android en 2026
Les questions Kotlin Flow vs StateFlow vs SharedFlow que posent les recruteurs Android en 2026, avec des réponses claires, un tableau comparatif et du code prêt pour la production.

Android 16 en 2026 : Nouvelles APIs, Desktop Mode et Questions d'Entretien
Analyse approfondie des changements d'Android 16 API 36 : edge-to-edge obligatoire, Desktop Mode, ProgressStyle, navigation predictive et questions d'entretien technique pour développeurs Android en 2026.