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

Compose Multiplatformってどうなんだろう? | Flutter × Kotlin Multiplatform by CyberAgent #7

Compose Multiplatformってどうなんだろう? | Flutter × Kotlin Multiplatform by CyberAgent #7

Seiya Kokushi

August 04, 2022
Tweet

More Decks by Seiya Kokushi

Other Decks in Technology

Transcript

  1. ᅳࢣ੣໵[email protected]
    גࣜձࣾ"CFNB57
    $PNQPTF.VMUJQMBUGPSNͬͯ
    Ͳ͏ͳΜͩΖ͏ʁ
    'MVUUFSº,PUMJO.VMUJQMBUGPSNCZ$ZCFS"HFOU

    View Slide

  2. $PNQPTF.VMUJQMBUGPSNʁ

    View Slide

  3. $PNQPTFͱ͍͑͹
    IUUQTEFWFMPQFSBOESPJEDPNKFUQBDLDPNQPTF

    View Slide

  4. Ͱ͕͢

    View Slide

  5. IUUQTXXXKFUCSBJOTDPNMQDPNQPTFNQQ

    View Slide

  6. $PNQPTF.VMUJQMBUGPSNͱ͸
    w +FUQBDL$PNQPTFΛ.VMUJQMBUGPSN %FTLUPQɺ8FC
    Ͱར༻Ͱ͖Δ
    w CZ+FU#SBJOT
    w ࠷৽ͷTUBCMFόʔδϣϯ͸

    View Slide

  7. +FU#SBJOTDPNQPTFKC

    View Slide

  8. αϯϓϧͷҰ෦Λ঺հ💁

    View Slide

  9. View Slide

  10. ͓෼͔Γ͍͚ͨͩͨͩΖ͏͔ʁ👀

    View Slide

  11. View Slide

  12. J1IPOF্Ͱಈ͍͍ͯΔ 🥹

    View Slide

  13. w WBMQIBEFW͋ͨΓ͔ΒJ04Ͱ΋ಈ͔ͤΔ
    w ࠷৽͸WBMQIBEFW ࣌఺

    ͭ͡͸

    View Slide

  14. αϯϓϧΛ΋ͱʹਂ΅͍ͬͯ͘👷

    View Slide

  15. +FU#SBJOTDPNQPTFKC
    📁FYQFSJNFOUBM
    📁FYBNQMFT
    📁DIBUNQQ
    📁GBMMJOHCBMMTNQQ
    📁NJOFTXFFQFS

    View Slide

  16. +FU#SBJOTDPNQPTFKC
    📁FYQFSJNFOUBM
    📁FYBNQMFT
    📁DIBUNQQ👈
    📁GBMMJOHCBMMTNQQ
    📁NJOFTXFFQFS

    View Slide

  17. View Slide

  18. plugins {
    id("com.android.application")
    kotlin("multiplatform")
    id("org.jetbrains.compose")
    }
    w $PNQPTF.VMUJQMBUGPSNͷϏϧυઃఆΛߦ͏QMVHJO
    w DPNQPTFKCHSBEMFQMVHJOTDPNQPTF
    w $PNQPTF1MVHJOLU
    IUUQTHJUIVCDPN+FU#SBJOTDPNQPTFKCCMPCNBTUFSHSBEMFQMVHJOTDPNQPTFTSDNBJOLPUMJO
    PSHKFUCSBJOTDPNQPTF$PNQPTF1MVHJOLU
    chat-mpp/build.gradle.kts

    View Slide

  19. chat-mpp/gradle.properties
    compose.version=1.2.0-alpha01-dev725
    pluginManagement {
    // …
    plugins {
    val kotlinVersion = extra["kotlin.version"] as String
    val agpVersion = extra["agp.version"] as String
    val composeVersion = extra["compose.version"] as String
    kotlin("jvm").version(kotlinVersion)
    kotlin("multiplatform").version(kotlinVersion)
    kotlin("android").version(kotlinVersion)
    id("com.android.base").version(agpVersion)
    id("com.android.application").version(agpVersion)
    id("com.android.library").version(agpVersion)
    id("org.jetbrains.compose").version(composeVersion)
    }
    }
    chat-mpp/settings.gradle.kts
    w CVJMEHSBEMFLUTͷQMVHJOʹόʔδϣϯࢦఆͯ͠΋0,

    View Slide

  20. repositories {
    google()
    mavenCentral()
    maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
    }
    IUUQTNBWFOQLHKFUCSBJOTTQBDFQVCMJDQDPNQPTFEFWPSHKFUCSBJOTDPNQPTF
    PSHKFUCSBJOTDPNQPTFHSBEMFQMVHJO
    w $PNQPTF.VMUJQMBUGPSNͷ(SBEMF1MVHJO΍

    DPNQPTFͷΞʔςΟϑΝΫτͷऔಘઌΛࢦఆ
    chat-mpp/build.gradle.kts

    View Slide

  21. kotlin {
    android()
    iosX64("uikitX64") {
    binaries {
    executable() {
    entryPoint = "main"
    freeCompilerArgs += listOf(
    "-linker-option", "-framework", "-linker-option", "Metal",
    "-linker-option", "-framework", "-linker-option", "CoreText",
    "-linker-option", "-framework", "-linker-option", "CoreGraphics"
    )
    }
    }
    }
    iosArm64("uikitArm64") {
    binaries {
    executable() {
    entryPoint = "main"
    freeCompilerArgs += listOf(
    "-linker-option", "-framework", "-linker-option", "Metal",
    "-linker-option", "-framework", "-linker-option", "CoreText",
    "-linker-option", "-framework", "-linker-option", "CoreGraphics"
    )
    }
    }
    }
    // …
    }
    w ͳͥVJLJU9YY͕͍͍ͭͯΔʁ
    chat-mpp/build.gradle.kts

    View Slide

  22. IUUQTHJUIVCDPN+FU#SBJOTDPNQPTFKCCMPCEDFGDEBCFCGDCCDE
    HSBEMFQMVHJOTDPNQPTFTSDNBJOLPUMJOPSHKFUCSBJOTDPNQPTFFYQFSJNFOUBMVJLJUJOUFSOBM
    DPO
    fi
    HVSF&YQFSJNFOUBM6JLJU"QQMJDBUJPOLU-
    internal fun Project.configureExperimentalUikitApplication(
    mppExt: KotlinMultiplatformExtension,
    application: ExperimentalUiKitApplication
    ) {
    // …
    tasks.register(
    "packComposeUikitApplicationForXCode",
    ExperimentalPackComposeApplicationForXCodeTask::class.java
    ) { packTask ->
    val targetType = project.providers.environmentVariable("SDK_NAME").map {
    if (it.startsWith("iphoneos"))
    UikitTarget.Arm64
    else UikitTarget.X64
    }.orElse(UikitTarget.X64)
    val buildType = project.providers.environmentVariable("CONFIGURATION").map {
    if (it.equals("release", ignoreCase = true)) NativeBuildType.RELEASE
    else NativeBuildType.DEBUG
    }.orElse(NativeBuildType.DEBUG)
    val target = mppExt.targets.getByName(targetType.get().targetName) as KotlinNativeTarget
    // ...
    }
    configureIosDeployTasks(application)
    }
    w σόΠεʹΞϓϦΛσϓϩΠ͢ΔλΠϛϯάͰ

    6JLJU5BSHFUͰఆٛ͞Ε໊ͨલͱҰக͢ΔλʔήοτΛͱΓʹ͍͘
    chat-mpp/build.gradle.kts

    View Slide

  23. kotlin {
    // …
    sourceSets {
    val commonMain by getting {
    dependencies {
    implementation(compose.ui)
    implementation(compose.foundation)
    implementation(compose.material)
    implementation(compose.runtime)
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3")
    }
    }
    // …
    val androidMain by getting {
    dependsOn(commonMain)
    kotlin.srcDirs("src/jvmMain/kotlin")
    dependencies {
    api("androidx.appcompat:appcompat:1.4.1")
    implementation("androidx.activity:activity-compose:1.4.0")
    }
    }
    // …
    val nativeMain by creating {
    dependsOn(commonMain)
    }
    val uikitMain by creating {
    dependsOn(nativeMain)
    }
    val uikitX64Main by getting {
    dependsOn(uikitMain)
    }
    val uikitArm64Main by getting {
    dependsOn(uikitMain)
    }
    }
    }
    chat-mpp/build.gradle.kts

    View Slide

  24. kotlin {
    // …
    sourceSets {
    val commonMain by getting {
    dependencies {
    implementation(compose.ui)
    implementation(compose.foundation)
    implementation(compose.material)
    implementation(compose.runtime)
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3")
    }
    }
    // …
    val androidMain by getting {
    dependsOn(commonMain)
    kotlin.srcDirs("src/jvmMain/kotlin")
    dependencies {
    api("androidx.appcompat:appcompat:1.4.1")
    implementation("androidx.activity:activity-compose:1.4.0")
    }
    }
    // …
    val nativeMain by creating {
    dependsOn(commonMain)
    }
    val uikitMain by creating {
    dependsOn(nativeMain)
    }
    val uikitX64Main by getting {
    dependsOn(uikitMain)
    }
    val uikitArm64Main by getting {
    dependsOn(uikitMain)
    }
    }
    }
    DPNQPTFؔ࿈ͷ
    ϥΠϒϥϦͷґଘΛ௥Ճ
    chat-mpp/build.gradle.kts

    View Slide

  25. IUUQTHJUIVCDPN+FU#SBJOTDPNQPTFKCCMPCEDFGDEBCFCGDCCDE
    HSBEMFQMVHJOTDPNQPTFTSDNBJOLPUMJOPSHKFUCSBJOTDPNQPTF$PNQPTF1MVHJOLU-
    w Ͳ͜Λࢦ͍ͯ͠Δͷ͔ʁ
    w $PNQPTF1MVHJOLUͷதʹఆ͕ٛ͋Δ
    w OBWJHBUJPOͳͲ͸·ͩͳ͍
    object Dependencies {
    val desktop = DesktopDependencies
    val animation get() = composeDependency("org.jetbrains.compose.animation:animation")
    val animationGraphics get() = composeDependency("org.jetbrains.compose.animation:animation-graphics")
    val foundation get() = composeDependency("org.jetbrains.compose.foundation:foundation")
    val material get() = composeDependency("org.jetbrains.compose.material:material")
    @ExperimentalComposeLibrary
    val material3 get() = composeDependency("org.jetbrains.compose.material3:material3")
    val runtime get() = composeDependency("org.jetbrains.compose.runtime:runtime")
    val ui get() = composeDependency("org.jetbrains.compose.ui:ui")
    @ExperimentalComposeLibrary
    val uiTestJUnit4 get() = composeDependency("org.jetbrains.compose.ui:ui-test-junit4")
    val uiTooling get() = composeDependency("org.jetbrains.compose.ui:ui-tooling")
    val preview get() = composeDependency("org.jetbrains.compose.ui:ui-tooling-preview")
    val materialIconsExtended get() = composeDependency("org.jetbrains.compose.material:material-icons-extended")
    val web: WebDependencies get() = WebDependencies
    }
    chat-mpp/build.gradle.kts

    View Slide

  26. IUUQTHJUIVCDPN+FU#SBJOTDPNQPTFKCCMPCEDFGDEBCFCGDCCDE
    HSBEMFQMVHJOTDPNQPTFTSDNBJOLPUMJOPSHKFUCSBJOTDPNQPTF$PNQPTF1MVHJOLU-
    w PSHKFUCSBJOTDPNQPTFYYY͸BOESPJEYΛϑΥʔΫ͍ͯ͠Δ
    w +FU#SBJOT͕.VMUJQMBUGPSNରԠ
    w +FU#SBJOTBOESPJEY
    object Dependencies {
    val desktop = DesktopDependencies
    val animation get() = composeDependency("org.jetbrains.compose.animation:animation")
    val animationGraphics get() = composeDependency("org.jetbrains.compose.animation:animation-graphics")
    val foundation get() = composeDependency("org.jetbrains.compose.foundation:foundation")
    val material get() = composeDependency("org.jetbrains.compose.material:material")
    @ExperimentalComposeLibrary
    val material3 get() = composeDependency("org.jetbrains.compose.material3:material3")
    val runtime get() = composeDependency("org.jetbrains.compose.runtime:runtime")
    val ui get() = composeDependency("org.jetbrains.compose.ui:ui")
    @ExperimentalComposeLibrary
    val uiTestJUnit4 get() = composeDependency("org.jetbrains.compose.ui:ui-test-junit4")
    val uiTooling get() = composeDependency("org.jetbrains.compose.ui:ui-tooling")
    val preview get() = composeDependency("org.jetbrains.compose.ui:ui-tooling-preview")
    val materialIconsExtended get() = composeDependency("org.jetbrains.compose.material:material-icons-extended")
    val web: WebDependencies get() = WebDependencies
    }
    chat-mpp/build.gradle.kts

    View Slide

  27. chat-mpp/build.gradle.kts
    IUUQTHJUIVCDPN+FU#SBJOTDPNQPTFKCCMPCEDFGDEBCFCGDCCDE
    HSBEMFQMVHJOTDPNQPTFTSDNBJOLPUMJOPSHKFUCSBJOTDPNQPTF$PNQPTF1MVHJOLU-
    w DPNQPTFKCDPNQPTF಺ͰTVCNPEVMFͱͯ͠ࢀর
    w DPNQPTFFYUFSOBMEPDMBWB
    w DPNQPTFGSBNFXPSLTTVQQPSU
    w DPNQPTFHPMEFO
    w DPNQPTFQSFCVJMUTBOESPJEYJOUFSOBM
    object Dependencies {
    val desktop = DesktopDependencies
    val animation get() = composeDependency("org.jetbrains.compose.animation:animation")
    val animationGraphics get() = composeDependency("org.jetbrains.compose.animation:animation-graphics")
    val foundation get() = composeDependency("org.jetbrains.compose.foundation:foundation")
    val material get() = composeDependency("org.jetbrains.compose.material:material")
    @ExperimentalComposeLibrary
    val material3 get() = composeDependency("org.jetbrains.compose.material3:material3")
    val runtime get() = composeDependency("org.jetbrains.compose.runtime:runtime")
    val ui get() = composeDependency("org.jetbrains.compose.ui:ui")
    @ExperimentalComposeLibrary
    val uiTestJUnit4 get() = composeDependency("org.jetbrains.compose.ui:ui-test-junit4")
    val uiTooling get() = composeDependency("org.jetbrains.compose.ui:ui-tooling")
    val preview get() = composeDependency("org.jetbrains.compose.ui:ui-tooling-preview")
    val materialIconsExtended get() = composeDependency("org.jetbrains.compose.material:material-icons-extended")
    val web: WebDependencies get() = WebDependencies
    }

    View Slide

  28. compose.experimental {
    // …
    uikit.application {
    bundleIdPrefix = "org.jetbrains"
    projectName = "Chat"
    deployConfigurations {
    simulator("IPhone8") {
    //Usage: ./gradlew iosDeployIPhone8Debug
    device = IOSDevices.IPHONE_8
    }
    simulator("IPad") {
    //Usage: ./gradlew iosDeployIPadDebug
    device = IOSDevices.IPAD_MINI_6th_Gen
    }
    connectedDevice("Device") {
    //First need specify your teamId here, or in local.properties (compose.ios.teamId=***)
    //teamId="***"
    //Usage: ./gradlew iosDeployDeviceRelease
    }
    }
    }
    }
    w σόΠεΛม͑Δ͜ͱ΋Մೳ ͔ͳΓଟ͘ͷσόΠεʹରԠ

    w *04%FWJDFTLUʹҰཡ͕͋Δ
    IUUQTHJUIVCDPN+FU#SBJOTDPNQPTFKCCMPCNBTUFSHSBEMFQMVHJOTDPNQPTFTSDNBJOLPUMJOPSH
    KFUCSBJOTDPNQPTFFYQFSJNFOUBMETM*04%FWJDFTLU
    chat-mpp/build.gradle.kts

    View Slide

  29. commonMain/ChatApp.kt
    @Composable
    fun ChatApp() {
    // …
    MaterialTheme {
    Box(modifier = Modifier.fillMaxSize()) {
    Scaffold(
    topBar = {
    TopAppBar(
    title = { Text("Chat sample") }
    )
    }
    ) {
    Column(
    modifier = Modifier.fillMaxSize()
    ) {
    Box(Modifier.weight(1f)) {
    Messages(state.messages)
    }
    SendMessage { text ->
    store.send(
    Action.SendMessage(
    Message(myUser, timeMs = timestampMs(), text)
    )
    )
    }
    }
    }
    }
    }
    // …
    }

    View Slide

  30. androidMain/kotlin/MainActivity.kt
    class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
    ChatApp()
    }
    }
    }

    View Slide

  31. uikitMain/kotlin/main.uikit.kt
    fun main() {


    val args = emptyArray()


    memScoped {


    val argc = args.size + 1


    val argv = (arrayOf("skikoApp") + args).map { it.cstr.ptr }.toCValues()


    autoreleasepool {


    UIApplicationMain(argc, argv, null, NSStringFromClass(SkikoAppDelegate))


    }


    }


    }


    class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {


    companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta


    @ObjCObjectBase.OverrideInit


    constructor() : super()


    private var _window: UIWindow? = null


    override fun window() = _window


    override fun setWindow(window: UIWindow?) {


    _window = window


    }


    override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map?): Boolean {


    window = UIWindow(frame = UIScreen.mainScreen.bounds)


    window!!.rootViewController = Application("Chat") {


    ChatApp()


    }


    window!!.makeKeyAndVisible()


    return true


    }


    }


    IUUQTHJUIVCDPN+FU#SBJOTBOESPJEYCMPCFDEDGFFGDCG
    DPNQPTFVJVJTSDVJLJU.BJOLPUMJOBOESPJEYDPNQPTFVJVJLJU$PNQPTF8JOEPXVJLJULU-

    View Slide

  32. uikitMain/kotlin/main.uikit.kt
    fun main() {


    val args = emptyArray()


    memScoped {


    val argc = args.size + 1


    val argv = (arrayOf("skikoApp") + args).map { it.cstr.ptr }.toCValues()


    autoreleasepool {


    UIApplicationMain(argc, argv, null, NSStringFromClass(SkikoAppDelegate))


    }


    }


    }


    class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {


    companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta


    @ObjCObjectBase.OverrideInit


    constructor() : super()


    private var _window: UIWindow? = null


    override fun window() = _window


    override fun setWindow(window: UIWindow?) {


    _window = window


    }


    override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map?): Boolean {


    window = UIWindow(frame = UIScreen.mainScreen.bounds)


    window!!.rootViewController = Application("Chat") {


    ChatApp()


    }


    window!!.makeKeyAndVisible()


    return true


    }


    }


    IUUQTHJUIVCDPN+FU#SBJOTBOESPJEYCMPCFDEDGFFGDCG
    DPNQPTFVJVJTSDVJLJU.BJOLPUMJOBOESPJEYDPNQPTFVJVJLJU$PNQPTF8JOEPXVJLJULU-
    DPNQPTFͱ6*,JUͷੈքΛͭͳ͙
    🤝

    View Slide

  33. uikitMain/kotlin/main.uikit.kt
    w "QQMJDBUJPOͷத਎͸ʁ
    w TLJLPͰ6*7JFX$POUSPMMFS্ʹDPNQPTFΛඳը
    w TLJLPʁ
    w +FU#SBJOTTLJLP
    w 4LJBΛ,PUMJOͰѻ͏ͨΊͷϥΠϒϥϦ

    View Slide

  34. 🎉🎉🎉

    View Slide

  35. w DPNQPTFBMQIBͷ"1*͕J04Ͱ΋࢖͑Δ
    w DPNNPO.BJOͷDPNQPTBCMFͷQSFWJFX͸·ͩͰ͖ͳͦ͏
    w 4LJBΛ࢖ͬͯඳը͢ΔͷͰϨϯμϦϯάύϑΥʔϚϯεͷน͕
    ॴײ
    🤟
    💪

    View Slide

  36. ͓·͚
    IUUQTHJUIVCDPN+FU#SBJOTBOESPJEYDPNNJUCBF
    ff
    EEDEFFE
    w 4XJGU6*͔ΒDPNQPTF͕ඳը͞Εͨ6*7JFX$POUSPMMFSΛදࣔͨ͠Γ

    ͦͷٯ΋ࢼ͍ͯ͠Δʁ
    EJNBBWEFFWTXJGUVJJOUFSPQ

    View Slide

  37. w DPNQPTFKCʹ͸%FTLUPQ΍8FCͷνϡʔτϦΞϧ΋ॆ࣮
    w ݁ߏ͍ΖΜͳ͜ͱͰ͖Δ
    w DPNQPTFKCDPNQPOFOUTʹ͸ಠࣗͷ

    DPNQPTBCMFؔ਺΋͋Δ
    w "OJNBUFE*NBHFɺ4QMJU1BOF
    ͓·͚

    View Slide

  38. w BOESPJEYͷJ04޲͚ରԠ͕ਐΜͰ͍ΔCZ(PPHMF
    w DPMMFDUJPOɺEBUBTUPSFNPSF
    ͓·͚

    View Slide

  39. ࠓޙ΋,PUMJO.VMUJQMBUGPSNͱ
    DPNQPTFָ͕͠Έ👾

    View Slide