Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Jouons de la musique avec Compose Multiplatform

Jouons de la musique avec Compose Multiplatform

Préparez vos partitions, dans cette conférence, nous mettrons l’accent sur la créativité en concevant et en développant un lecteur de musique entièrement fonctionnel en Kotlin avec Compose Multiplatform. Au programme, une interface utilisateur qui groove comme jamais, un contrôleur de musique toujours au tempo, et surtout… une session qui vous fera vibrer au rythme de Kotlin Multiplatform 💃🕺

Avatar for Renaud MATHIEU

Renaud MATHIEU

June 12, 2025
Tweet

More Decks by Renaud MATHIEU

Other Decks in Programming

Transcript

  1. Titre 💰 Et devenons riches Jouons de la musique avec

    Compose Multiplatform Kotlin Kotlin Multiplatform Jetpack Compose Compose Multiplatform Audio GrooveBox
  2. JVM Applications serveur Application desktop Android Langage o ffi ciel

    Applications mobiles Multiplaform Code partagé iOS, Android, Web JavaScript Applications web Langage moderne, concis, interopérable 100% compatible avec Java, développé par Jetbrains Kotlin Null Safety Types nullable (String?) Elvis operator ( ?:) Smart casting Concision Inférence de type Data class Extension functions Intéropérabilité Même bytecode JVM Bibliothèques existantes Caractéristiques clés Coroutines Programmation asynchrone Suspend functions Outils IntelliJ IDEA, Android Studio Gradle, Maven Frameworks Spring Boot, Ktor Jetpack Compose
  3. Kotlin Multiplatform Quickstart • Install Android Studio ou IntelliJ IDEA

    • Install Kotlin Multiplatform IDE plugin • Set up ANDROID_HOME • Install Xcode
  4. Kotlin Multiplatform Con fi guration Target SourceSet JVM, Android, iOS,

    JavaScript, … commonMain, jvmMain, androidMain
  5. Kotlin Multiplatform Con fi guration Target SourceSet kotlin { androidTarget

    { @OptIn(ExperimentalKotlinGradlePluginApi :: class) compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } } iosX64() iosArm64() iosSimulatorArm64() jvm() sourceSets { androidMain.dependencies { } commonMain.dependencies { } commonTest.dependencies { } jvmMain.dependencies { } } }
  6. Kotlin Multiplatform Principe Expect / Actual commonMain androidMain iosMain jvmMain

    interface Platform { val name: String } expect fun getPlatform(): Platform class JVMPlatform : Platform { override val name: String = "Java ${System.getProperty("java.version")}" } actual fun getPlatform(): Platform = JVMPlatform() class AndroidPlatform : Platform { override val name: String = "Android ${Build.VERSION.SDK_INT}" } actual fun getPlatform(): Platform = AndroidPlatform() class IOSPlatform: Platform { override val name: String = UIDevice.currentDevice.systemName() } actual fun getPlatform(): Platform = IOSPlatform()
  7. UI Impérative Décrit comment faire quelque chose, étape par étape

    ❌ Complexité de la gestion d’état ❌ Bugs di f fi ciles à traquer ❌ Performance imprévisible UI Déclarative Décrit ce que l’on veut comme résultat fi nal ✓ UI = f(State) ✓ Pas de mutation directe des vues ✓ Prévisibilité et fi abilité ✓ Un seul état = une seule UI ✓ Recomposition automatique ✓ Performance optimisée (smart recomposition, skip des composables) composables)
  8. UI Impérative UI Déclarative override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_main) textView = findViewById(R.id.textView) button = findViewById(R.id.button) button.setOnClickListener { counter ++ updateUI() } } @Composable fun CounterScreen() { var counter by remember { mutableStateOf(0) } Column { Text("Count: $counter") Button( onClick = { counter ++ }, ) { Text("Increment") } } }
  9. Jetpack Compose Du code Kotlin au rendu à l’écran Code

    source Kotlin Compilation Bytecode Android Processus de rendu runtime
  10. Jetpack Compose Du code Kotlin au rendu à l’écran Code

    source Kotlin Compilation Bytecode Android Processus de rendu runtime
  11. Compilation fun Example(name: String, composer: Composer?, changed: Int) { //

    Début du groupe composable, clé unique associée à Example() val startToken = composer.startRestartGroup( /* key */ 123456) // Propagation du bitmask de changement pour le paramètre 'name' var dirty = changed if (dirty and 0x0E == 0) { dirty = dirty or if (composer.changed(name)) 0x02 else 0x04 } // Vérification des changements : si pas de changement et skipping possible, on saute le corps if ((dirty and 0x0B) != 0 || !composer.skipping) { // Corps réel de la fonction Text("Hello $name", composer, /* changed */ 0x0) } else { composer.skipToGroupEnd() } // Fin du groupe composable, avec enregistrement de la lambda de recomposition composer.endRestartGroup() ?. updateScope { comp -> Example(name, comp, dirty or 0x01) } }
  12. Compilation 1. Ajout de paramètres synthétiques Toute fonction marquée @Composable

    voit sa signature enrichie de paramètres supplémentaires invisibles pour le développeur. • Composer: orchestrateur de la composition • changed: entier de bits servant à coder l’état des paramètres fun Example(name: String, composer: Composer?, changed: Int) { … }
  13. Compilation Slot Table La Slot Table est une structure de

    données optimisée qui permet de : • Enregistrer la hiérarchie des Composables. • Optimiser la recomposition (éviter de tout reconstruire à chaque changement). • Faire correspondre les Composables actuels aux versions précédentes
  14. Compilation 2. Groupe de composition et clés Le plugin réécrit

    le corps de chaque fonction Composable pour entourer son contenu de balises de groupe. • composer.startRestartGroup(key): en début de fonction • composer.endRestartGroup() : à la fi n fun Example(name: String, composer: Composer?, changed: Int) { // Début du groupe composable, clé unique associée à Example() val startToken = composer.startRestartGroup( /* key */ 123456) … // Fin du groupe composable, avec enregistrement de la lambda de recomposition composer.endRestartGroup() }
  15. Compilation 3. Propagation du statut des paramètres Le compilateur génère

    du code pour propager et évaluer les changements de paramètres. • composer.changed(param): skip automatique si paramètres stables et identiques // Propagation du bitmask de changement pour le paramètre 'name' var dirty = changed if (dirty and 0x0E == 0) { dirty = dirty or if (composer.changed(name)) 0x02 else 0x04 } // Vérification des changements : si pas de changement et skipping possible, on saute le corps if ((dirty and 0x0B) != 0 || !composer.skipping) { // Corps réel de la fonction Text("Hello $name", composer, /* changed */ 0x0) } else { composer.skipToGroupEnd() }
  16. Compilation 4. Gestion des appels à remember et stockage de

    l’état Permet de conserver du « state local » entre deux recomposition en s’appuyant sur la mémoire de composition plutôt que sur des variable globales • var count by remember { mutableStateOf(1) } -> count = composer.cache(true) { mutableStateOf(1) } Le composer va alors soit renvoyer la valeur mémorisée existante (stockée dans la Slots Table) soit évaluer la lambda et stocker le résultat.
  17. Compilation 5. Structure de contrôle conditionnelles Le compilateur recompose un

    arbre UI et doit donc anticiper les variations de structure causées par les conditions (if/else) et les boucles (for, repeat, liste d’items…). • startReplaceableGroup: re f lète le fait que le contenu peut être remplacé d’une exécution à l’autre Préserver l’intégrité de la Slot Table
  18. Compilation 6. Scope de recomposition (restartable) Le compilateur recompose un

    arbre UI et doit donc anticiper les variations de structure causées par les conditions (if/else) et les boucles (for, repeat, liste d’items…). • composer.endRestartGroup()?.updateScope { ... }: fonction de rappel qui permet de relancer ce Composable plus tard si un état observé à l’intérieur change.
  19. Compilation fun Example(name: String, composer: Composer?, changed: Int) { //

    Début du groupe composable, clé unique associée à Example() val startToken = composer.startRestartGroup( /* key */ 123456) // Propagation du bitmask de changement pour le paramètre 'name' var dirty = changed if (dirty and 0x0E == 0) { dirty = dirty or if (composer.changed(name)) 0x02 else 0x04 } // Vérification des changements : si pas de changement et skipping possible, on saute le corps if ((dirty and 0x0B) != 0 || !composer.skipping) { // Corps réel de la fonction Text("Hello $name", composer, /* changed */ 0x0) } else { composer.skipToGroupEnd() } // Fin du groupe composable, avec enregistrement de la lambda de recomposition composer.endRestartGroup() ?. updateScope { comp -> Example(name, comp, dirty or 0x01) } }
  20. Jetpack Compose Du code Kotlin au rendu à l’écran Code

    source Kotlin Compilation Bytecode Android Processus de rendu runtime
  21. Processus de rendu runtime Une fois l’application compilée, c’est le

    runtime de Jetpack Compose qui prend en charge l’exécution des fonctions Composables et la mise à jour de l’interface utilisateur. Ce runtime s’appuie sur un concept central. Les di f férents composants sont : • Slot Table • Composer, • Recomposer.
  22. Processus de rendu runtime @Composable fun CounterScreen() { var counter

    by remember { mutableStateOf(0) } Column { Text("Count: $counter") Button( onClick = { counter ++ }, ) { Text("Increment") } } }
  23. Processus de rendu runtime @Composable fun CounterScreen() { var counter

    by remember { mutableStateOf(0) } Column { Text("Count: $counter") Button( onClick = { counter ++ }, ) { Text("Increment") } } }
  24. Compose Multiplatform Du code Kotlin au rendu à l’écran Code

    source partagé Compilation spécialisée Plateformes natives Processus de rendu runtime
  25. Compose Multiplatform (iOS) utilise le même compilateur Compose mais ciblant

    Kotlin/ Native. Le code @Composable est donc également transformé via les mêmes mécanismes IR, assurant une sémantique identique (insertion de paramètres de composer, etc.). Le pipeline iOS génère un XCFramework contenant le code natif et les ressources Compose. Compilation spécialisée
  26. Di f ferences d’implémentation Sur iOS, en l’absence de Choreographer,

    Compose Multiplatform utilise son propre mécanisme de scheduling. Par défaut, les recompositions et le rendu s’e f fectuent sur le thread principal iOS Gestion de la mémoire et du multithreading JVM/ART béné fi cie d’un GC concurrent qui minimise les pauses Kotlin/Native utilise un GC mark-and-sweep non concurrent Processus de rendu runtime
  27. Rendu sur Android Jetpack Compose ne s’appuie pas sur les

    Android Views classiques pour dessiner chaque widget. Il opère principalement via une hiérarchie de LayoutNodes propres à Compose et dessine sur le Canvas Android. Rendu sur iOS • L’UI Compose est dessinée via la bibliothèque graphique Skia rendue sur une surface native iOS. • JetBrains fournit pour cela une couche d’intégration appelée Skiko. • Compose dessine les éléments UI sur un Canvas Skia et s’appuie sur une UIView « conteneur » Processus de rendu runtime
  28. Compose Multiplatform Du code Kotlin au rendu à l’écran 🤖

    Android Kotlin/JVM Compilation 1 Compose Compiler Plugin 2 DEX/APK Generation 3 Code CommonMain + androidMain compilé vers bytecode JVM Transformation des @Composable avec optimisations Android spécifiques Bytecode → DEX → APK avec Android Gradle Plugin 🔧 Stack Technique ART Runtime Canvas API Skia Engine Android Views 🎯 Spécificités • Interop avec Views natives • Architecture Components • Material Design intégré • Optimisations ART
  29. Compose Multiplatform Du code Kotlin au rendu à l’écran 🍎

    iOS Kotlin/Native Compilation 1 Compose Compiler + Skiko 2 iOS Framework/App 3 Code CommonMain + iosMain compilé vers code natif (LLVM) Transformation + intégration Skia pour iOS Génération framework iOS ou application complète 🔧 Stack Technique LLVM Skia iOS UIKit Interop Metal API 🎯 Spécificités • Interop avec UIKit • SwiftUI bridge possible • iOS Design Guidelines • Metal rendering optimisé
  30. Compose Multiplatform Du code Kotlin au rendu à l’écran 🖥

    Desktop Kotlin/JVM Compilation 1 Compose Desktop Runtime 2 JAR/Native Package 3 Code CommonMain + desktopMain compilé vers bytecode JVM Intégration avec Skia-JVM et Swing/ AWT Distribution JAR ou package natif (DMG, MSI, DEB) 🔧 Stack Technique JVM Skia JVM SWING / AWT OpenGL 🎯 Spécificités • Interop Swing/AWT • Menu bars natifs • Fenêtres multi-écrans • Raccourcis clavier OS
  31. Kermit Touchlab ✓ Logger multiplatform ✓ Log once, write anywhere

    ✓ Crashlytics GrooveBox™ Le projet est-il évolutif sur le long terme ? Koin 4.1 Kotzilla ✓ Dé fi nition de module commun ✓ Implémentation spéci fi que ✓ Injection dans le code (Compose 💚) ✓ Preview ✓ Tests Gradle Gradle ✓ Android / JVM friendly ✓ Maven ✓ Plugins ❌ iOS
  32. Conclusion Inconvénients de Compose Multiplatform • Maturité inégale selon les

    plateformes • Look & Feel non natif • Taille des binaires • Outils et écosystème limité • Performance iOS
  33. Conclusion Comparaison avec les autres technologies « Cross-Platform » Compose

    Multiplatform JetBrains/Google - Kotlin ✓ Performance native réelle ✓ Même écosystème qu’Android ✓ Hot Reload ✓ Accès API natives direct ✓ Debugging uni fi é K Flutter Google - Dart ✓ Performance correcte ✓ Widgets riches ✓ Hot Reload ✓ Grande communauté ✓ Documentation complète F React Native Meta- JavaScript ✓ Ecosystème JS ✓ Composants natifs ✓ Fast refresh ✓ Flexibilité élevée ✓ Communauté massive RN
  34. Conclusion Pourquoi Compose Multiplatform se démarque ? Performance Native Réelle

    Contrairement aux solutions hybrides, Compose Multiplatform compile vers du code natif, o f frant des performances équivalentes aux applications natives pures. ⚡ Écosystème Android Uni fi é Réutilise directement les libraries, outils et connaissances Android existantes. Architecture, testing, debugging - tout reste familier. 🔧 UI Déclarative Moderne Jetpack Compose o f fre une approche déclarative moderne, plus simple et maintenable que les approches impératives traditionnelles. 🎨 Productivité Maximale Hot Reload instantané, preview en temps réel, debugging uni fi é et tooling intégré pour une expérience développeur optimale. 🚀 Support Multiplateforme Complet Android, iOS, Desktop (Windows, macOS, Linux) et Web avec une seule codebase Kotlin, sans compromis sur les fonctionnalités natives. 🌍 Avenir Garanti Soutenu par JetBrains et Google, Compose Multiplatform représente l'évolution naturelle du développement mobile moderne. 🔮