Slide 1

Slide 1 text

@bennegeek Fast Prototypes with Flutter + Kotlin/Native JB Lorenzo | #DroidKaigi

Slide 2

Slide 2 text

@bennegeek The Story OLX P&Tech Conference App, Berlin The Team

Slide 3

Slide 3 text

@bennegeek Mobi Rem Cruz Mobi Cristiano
 Madeira Mobi JB Lorenzo UX Prod Hero Karen Banzon Mana ger Ayelen Chavez Mana ger Caio Moritz

Slide 4

Slide 4 text

@bennegeek Mobi Rem Cruz Mobi Cristiano
 Madeira Mobi JB Lorenzo UX Prod Hero Karen Banzon Mana ger Ayelen Chavez Mana ger Caio Moritz * 3 devs * 2 managers * 1 ux/prod * /w real work * ~1 month deadline

Slide 5

Slide 5 text

@bennegeek Kotlin/Native Material Components Firebase

Slide 6

Slide 6 text

@bennegeek The Story UX Prod Hero

Slide 7

Slide 7 text

@bennegeek Outline: - Why? - How? (to Setup* (updated)) - How? (to link Flutter and Kotlin) - What? (pitfalls, considerations)

Slide 8

Slide 8 text

@bennegeek Why Flutter + Kotlin/Native

Slide 9

Slide 9 text

@bennegeek Architecture

Slide 10

Slide 10 text

@bennegeek How do I start What project structure works?

Slide 11

Slide 11 text

@bennegeek Flutter project structure

Slide 12

Slide 12 text

@bennegeek settings.gradle Flutter project structure How do I + Kotlin/Native

Slide 13

Slide 13 text

@bennegeek settings.gradle Flutter project structure Kotlin/Native

Slide 14

Slide 14 text

@bennegeek android \-- app \-- build.gradle \-- build.gradle \-- settings.gradle common a \-- build.gradle ios \-- Runner.xcodeproj lib

Slide 15

Slide 15 text

@bennegeek android \-- app \-- build.gradle \-- build.gradle \-- settings.gradle common a \-- build.gradle ios \-- Runner.xcodeproj lib

Slide 16

Slide 16 text

@bennegeek // settings.gradle include ':app a ' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } include ':common' plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } project(":common").projectDir = new File(“../common”) enableFeaturePreview('GRADLE_METADATA')

Slide 17

Slide 17 text

@bennegeek // settings.gradle include ':app a ' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } include ':common' plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } project(":common").projectDir = new File(“../common”) enableFeaturePreview('GRADLE_METADATA')

Slide 18

Slide 18 text

@bennegeek // settings.gradle include ':app a ' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } include ':common' plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } project(":common").projectDir = new File(“../common”) enableFeaturePreview('GRADLE_METADATA')

Slide 19

Slide 19 text

@bennegeek // settings.gradle include ':app a ' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } include ':common' plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } project(":common").projectDir = new File(“../common”) enableFeaturePreview('GRADLE_METADATA')

Slide 20

Slide 20 text

@bennegeek // settings.gradle include ':app a ' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } include ':common' plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } project(":common").projectDir = new File(“../common”) enableFeaturePreview('GRADLE_METADATA')

Slide 21

Slide 21 text

@bennegeek android \-- app \-- build.gradle \-- build.gradle \-- settings.gradle common a \-- build.gradle ios \-- Runner.xcodeproj lib

Slide 22

Slide 22 text

@bennegeek android \-- app \-- build.gradle \-- build.gradle \-- settings.gradle common a \-- build.gradle ios \-- Runner.xcodeproj lib

Slide 23

Slide 23 text

@bennegeek // build.gradle buildscript { ext.kotlin_version = '1.3.0' repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } // ...

Slide 24

Slide 24 text

@bennegeek // build.gradle buildscript { ext.kotlin_version = '1.3.0' repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } // ...

Slide 25

Slide 25 text

@bennegeek // build.gradle buildscript { ext.kotlin_version = '1.3.0' repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } // ...

Slide 26

Slide 26 text

@bennegeek android \-- app \-- build.gradle \-- build.gradle \-- settings.gradle common a \-- build.gradle ios \-- Runner.xcodeproj lib

Slide 27

Slide 27 text

@bennegeek android \-- app \-- build.gradle \-- build.gradle \-- settings.gradle common a \-- build.gradle ios \-- Runner.xcodeproj lib

Slide 28

Slide 28 text

@bennegeek // app / build.gradle dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation project(“:common”) }

Slide 29

Slide 29 text

@bennegeek android \-- app \-- build.gradle \-- build.gradle \-- settings.gradle common a \-- build.gradle ios \-- Runner.xcodeproj lib

Slide 30

Slide 30 text

@bennegeek android 1 \-- app \-- build.gradle \-- build.gradle \-- settings.gradle common \-- build.gradle ios \-- Runner.xcodeproj lib

Slide 31

Slide 31 text

@bennegeek // common / build.gradle apply plugin: 'kotlin-multiplatform' kotlin { targets { final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \ ? presets.iosArm64 : presets.iosX64 fromPreset(iOSTarget, 'iOS') { compilations.main.outputKinds('FRAMEWORK') } fromPreset(presets.jvm, 'android') } sourceSets { commonMain.dependencies { api 'org.jetbrains.kotlin:kotlin-stdlib-common' } androidMain.dependencies { api ‘org.jetbrains.kotlin:kotlin-stdlib' ¹ } } } // … task packForXCode(type: Sync) { finala File frameworkDir =aa new File(buildDir, "xcode-frameworks")a finala String mode =a project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG' inputs.property "mode", mode dependsOn kotlin.targets.iOS.compilations.main.linkTaskName("FRAMEWORK", mode) from { kotlin.targets.iOS.compilations.main.getBinary("FRAMEWORK", mode).parentFile } into frameworkDir doLast { new File(frameworkDir, 'gradlew').with { text =a "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$@\n" setExecutable(true) } } } tasks.build.dependsOn packForXCode ¹ Dissecting Stdlib https://www.youtube.com/watch?v=Fzt_9I733Yg

Slide 32

Slide 32 text

@bennegeek // common / a build.gradle apply plugin: 'kotlin-multiplatform' kotlin { targets { final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \ ? presets.iosArm64 : presets.iosX64 fromPreset(iOSTarget, 'iOS') { compilations.main.outputKinds('FRAMEWORK') } fromPreset(presets.jvm, 'android') } sourceSets { commonMain.dependencies { api 'org.jetbrains.kotlin:kotlin-stdlib-common' } androidMain.dependencies { api 'org.jetbrains.kotlin:kotlin-stdlib' } } } // ... task packForXCode(type: Sync) { final a File frameworkDir = aa new File(buildDir, "xcode-frameworks") a final a String mode = a project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG' inputs.property "mode", mode dependsOn kotlin.targets.iOS.compilations.main.linkTaskName("FRAMEWORK", mode) from { kotlin.targets.iOS.compilations.main.getBinary("FRAMEWORK", mode).parentFile } into frameworkDir doLast { new File(frameworkDir, 'gradlew').with { text = a "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '$ {rootProject.rootDir}'\n./gradlew \$@\n" setExecutable(true) } } } tasks.build.dependsOn packForXCode

Slide 33

Slide 33 text

@bennegeek android 1 \-- app \-- build.gradle 1 \-- build.gradle 1 \-- settings.gradle common \-- build.gradle ios \-- Runner.xcodeproj lib

Slide 34

Slide 34 text

@bennegeek android 1 \-- app \-- build.gradle 1 \-- build.gradle 1 \-- settings.gradle common \-- build.gradle ios \-- Runner.xcodeproj lib

Slide 35

Slide 35 text

@bennegeek # Runner.xcodeproj # Build Phase cd \”$SRCROOT/../common/build/xcode-frameworks\”\n ./gradlew :common:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}

Slide 36

Slide 36 text

@bennegeek # Runner.xcodeproj # Build Phase cd \”$SRCROOT/../common/build/xcode-frameworks\”\n ./gradlew :common:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}

Slide 37

Slide 37 text

@bennegeek Now we've setup - android/settings.gradle - android/build.gradle - ios/Runner.xcodeproj - common/build.gradle - framework in iOS Kotlin/Native

Slide 38

Slide 38 text

@bennegeek Now we can start coding in Flutter + Kotlin/Native ... How?

Slide 39

Slide 39 text

@bennegeek For gluing Flutter and Kotlin

Slide 40

Slide 40 text

@bennegeek Android // MainActivity.kt import common.doSomethingWith GeneratedPluginRegistrant.registerWith(this) MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result -> when (call.method) { "methodName" -> doSomethingWith(result) } }

Slide 41

Slide 41 text

@bennegeek iOS // AppDelegate.swift import YourFrameworkName // from Kotlin-Native // ... let channel = FlutterMethodChannel.init(name: "/channel", binaryMessenger: controller) channel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in // Process call.method and result }); GeneratedPluginRegistrant.register(with: self)

Slide 42

Slide 42 text

@bennegeek * Serializing and Deserializing * Companion in Swift * arm32 * No Bitcode * Hot reload * JVM libraries Pitfalls

Slide 43

Slide 43 text

@bennegeek The Result

Slide 44

Slide 44 text

@bennegeek Takeaways * Flutter is awesome for fast prototypes * You can replace Flutter with other cross platform UI frameworks while keeping your logic code in Kotlin/Native * Kotlin/Native are still in beta but is usable already though with some limitations * Kotlin/Native works well to reduce the amount of platform specific code * Some glue code needs to be written to pass around/ serialize objects * Don’t be afraid to try out new technologies (but …)

Slide 45

Slide 45 text

@bennegeek tech.olx.com

Slide 46

Slide 46 text

@bennegeek tech.olx.com