Share
Embed
KOTLINCONF IS BACK : after the party
오랜만에 KotlinConf 가 돌아왔습니다!
지난 2023년 4월 12일을 시작으로, 41개 나라에서 진행되고 있고 한국에서는 인프런과 함께 합니다. 총 91개의 세션이 준비되어 있는데, Kotlin 2.0과 새로운 컴파일러, 멀티플랫폼, 웹 및 백엔드 개발에 대한 주제가 눈에 띄었습니다.
오늘 이 자리에서는 KotlinConf 2023의 기조연설에서 언급된 내용을 함께 둘러보고, 가볍게 Kotlin Multiplatform + Compose 를 맛 볼 수 있는 코드를 공유하고자 합니다.
Arawn Park
April 25, 2023
Transcript
47 slides
-
݂֙ର ݂ѐਘର commit 543f02d23 Date: Sun Oct 10 19:34:30 2021
+0900 Add generated projects with spring and kotlin commit 226a51966 Date: Wed Oct 13 16:31:57 2021 +0900 Add Dockerfile and deploy manifest file commit 1d0525b88 Date: Wed Oct 13 18:31:41 2021 +0900 Make github actions commit 7476a1192 Date: Sat Oct 23 20:13:09 2021 +0900 Add servlet-filter based authentication processing commit 6390a5935 Date: Sat Oct 23 20:44:12 2021 +0900 Add meetup creation process and web-api ߅ਊӂ.apply { ࣗࣘ = "Ӕ݃" ౠ = "݈ ݆, nߓࣘ ࢤ" } -
যցف ঠաف Github projects use Kotlin as their primary language
Increase in Multiplatform libraries in 2002 of Android users are satisfied with Kotlin of the top 1,000 Android apps use Jetpack Compose of all kotlin users are satisfied with Kotlin of the top 1,000 Android apps are in Kotlin 1M 86 95 60 97 23 of the all daangn projects use Multiplatform 1 of the all daangn projects use Kotlin 10 -
,ஹੌ۞ܳ݅ա۰ݶCVJMEHSBEMFLUTܳইоࣁਃ https://techblog.woowahan.com/2532/ // 1.8.20 ࢚ kotlin { sourceSets.all { languageSettings
{ languageVersion = "2.0" } } } // 1.8.0 ೞ tasks.compileKotlin { kotlinOptions { useK2 = true } } tasks.compileTestKotlin { kotlinOptions { useK2 = true } } tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> { kotlinOptions { ... } } tasks.named("compileKotlin", org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile::class.java) { kotlinOptions { ... } } 4BNFBT -
؊׳ೞҊ߄ೠ࢜۽ޙߨ4ZOUBY ળ࠺غҊؘਃ Explicit Fields Context Receivers Name Based Destructuring Collection
Literals Static Extensions -
4UBUJD&YUFOTJPOT,5 val courses = File.readCSV(pathname = "inflearn/courses.csv") fun File.Companion.readCSV(pathname: String):
File { // processing } https://youtrack.jetbrains.com/issue/KT-11968 -
4UBUJD&YUFOTJPOT,5 val courses = File.readCSV(pathname = "inflearn/courses.csv") fun File.Companion.readCSV(pathname: String):
File { // processing } https://youtrack.jetbrains.com/issue/KT-11968 -
4UBUJD&YUFOTJPOT,5 val courses = File.readCSV(pathname = "inflearn/courses.csv") fun File.Companion.readCSV(pathname: String):
File { // processing } fun File.static.readCSV(pathname: String): File { // processing } https://youtrack.jetbrains.com/issue/KT-11968 -
&YQMJDJU'JFMET,5 class Meetup { val members: Set<Member> get() = _members.toSet()
private val _members = mutableSetOf<Member>() fun join(member: Member): Meetup { _members.add(member) return this } // methods } -
&YQMJDJU'JFMET,5 class Meetup { val members: Set<Member> get() = _members.toSet()
private val _members = mutableSetOf<Member>() fun join(member: Member): Meetup { _members.add(member) return this } // methods } class Meetup { val member: Set<Member> field = mutableSetOf<Member>() // methods } -
$POUFYU3FDFJWFST,5 fun processRequest(context: ServiceContext, request: ServiceRequest) { val payload =
request.loadPayload(context) // processing } context(ServiceContext) fun processRequest(request: ServiceRequest) { val payload = request.loadPayload() // processing } context(ServiceContext) fun ServiceRequest.loadPayload(): Payload { // returning payload } -
/BNF#BTFE%FTUSVDUVSJOH,5 data class Person( val firstName: String, val lastName: String
) val (firstName, lastName) = Person("Brie", "Park") -
/BNF#BTFE%FTUSVDUVSJOH,5 data class Person( val firstName: String, val lastName: String
) val (firstName, lastName) = Person("Brie", "Park") val (lastName, firstName) = Person("Heidi", "Oh") -
$PMMFDUJPO-JUFSBMT,5 val courses = listOf("߄ ѐߊܳ ਤೠ ౣܽ ੑޙ", "Jetpack
Compose ੑޙ") val courses = ["߄ ѐߊܳ ਤೠ ౣܽ ੑޙ", "Jetpack Compose ੑޙ"] val scores = MutableSet [5.0, 4.8] -
@Composable internal fun GalleryScreen( galleryPage: GalleryPage, photoGallery: PhotoGallery, dependencies: Dependencies,
onClickPreviewPicture: (PictureData) -> Unit, onMakeNewMemory: () -> Unit ) { val pictures = dependencies.pictures var selected: PictureData by remember { mutableStateOf(pictures.first()) } LaunchedEffect(Unit) { ... } Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { ... } ... } @Composable private fun SquaresGalleryView( images: List<PictureData>, selectedImage: PictureData, onSelect: (PictureData) -> Unit, ) { LazyVerticalGrid( modifier = Modifier.padding(top = 4.dp), columns = GridCells.Adaptive(minSize = 130.dp), verticalArrangement = Arrangement.spacedBy(1.dp), horizontalArrangement = Arrangement.spacedBy(1.dp) ) { ... } } ... $PNQPTF.VMUJQMBUGPSN6*ۨਕܳࣗѐೡѱਃ https://www.jetbrains.com/lp/compose-multiplatform/ -
HSBEMFQSPQFSUJFT # Kotlin kotlin.code.style=official # MPP kotlin.mpp.stability.nowarn=true kotlin.mpp.enableCInteropCommonization=true kotlin.mpp.androidSourceSetLayoutVersion=2 #
Native kotlin.native.cacheKind=none kotlin.native.useEmbeddableCompilerJar=true kotlin.native.cocoapods.generate.wrapper=true kotlin.native.binary.memoryModel=experimental # Compose org.jetbrains.compose.experimental.uikit.enabled=true org.jetbrains.compose.experimental.macos.enabled=true org.jetbrains.compose.experimental.jscanvas.enabled=true # Android android.useAndroidX=true android.compileSdk=33 android.targetSdk=33 android.minSdk=24 # Versions jdk.version=11 kotlin.version=1.8.20 compose.version=1.4.0-dev-wasm06 ktor.version=2.3.0 agp.version=7.4.2 -
DPNNPOCVJMEHSBEMFLUT plugins { kotlin("multiplatform") kotlin("native.cocoapods") id("com.android.library") id("org.jetbrains.compose") } kotlin {
android() iosX64() iosArm64() iosSimulatorArm64() jvm("desktop") { ... } js(IR) { ... } wasm { ... } cocoapods { ... } sourceSets { val commonMain by getting { dependencies { implementation(compose.runtime) ... implementation("io.ktor:ktor-client-core:$ktorVersion") } } val commonTest by getting { ... } val androidMain by getting val androidInstrumentedTest by getting val iosMain by creating val desktopMain by getting val desktopTest by getting val wasmMain by getting val wasmTest by getting ... } } android { ... } -
DPNNPOBQQMJDBUJPO(SFFUJOH0QFSBUJPOTLU package kotlinconf.application import kotlinconf.net.URI import kotlinconf.net.http.ClientHttpRequestFactory import kotlinconf.net.http.HttpMethod interface
GreetingOperations { fun greeting(username: String): String } class LocalGreetingOperations : GreetingOperations { override fun greeting(username: String): String { return "Hello, $username" } } class RemoteGreetingOperations( private val clientHttpRequestFactory: ClientHttpRequestFactory, private val serverSchema: String = "http", private val serverHost: String = "localhost", private val serverPort: Int = 8080, ) : GreetingOperations { override fun greeting(username: String): String { val request = clientHttpRequestFactory.createRequest( uri = URI.parse("$s..Schema://$s..Host:$s..Port$PATH?username=$username"), httpMethod = HttpMethod.GET() ) return request.execute().body.decodeToString() } companion object { private const val PATH = "/greeting" } } -
DPNNPOVJ(SFFUJOH$PNQPTF"QQLU package kotlinconf.ui import androidx.compose..* import kotlinconf.application.GreetingOperations import kotlinconf.getPlatformName @Composable
fun GreetingComposeApp( greetingOperations: GreetingOperations, modifier: Modifier ) { val platformName = getPlatformName() Box(modifier = modifier, contentAlignment = Alignment.Center) { Column(modifier = Modifier.wrapContentSize()) { val greetingText = remember { mutableStateOf("Welcome...") } WriteBox( onSearchClicked = { username -> greetingText.value = try { greetingOperations.greeting(username = username) } catch (error: Throwable) { if (error.message == null) { "An unknown error occurred" } else { "Error: ${error.message?.lowercase()}" } } } ) TextPanel(text = greetingText) } } } @Composable private fun WriteBox(onSearchClicked: (String) -> Unit) { ... } @Composable private fun TextPanel(text: MutableState<String>) { ... } -
TFSWFS4FSWFS"QQMJDBUJPOLU package kotlinconf import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.context.support.beans import
org.springframework.web.servlet.config.annotation.CorsRegistry import org.springframework.web.servlet.config.annotation.WebMvcConfigurer @SpringBootApplication class ServerApplication fun main(args: Array<String>) { val applicationBeans = beans { } val webBeans = beans { bean { object : WebMvcConfigurer { override fun addCorsMappings(registry: CorsRegistry) { registry .addMapping("/**") .allowedOrigins("*") .allowedMethods("*") .allowedHeaders("*") } } } } runApplication<ServerApplication>(*args) { addInitializers(applicationBeans, webBeans) } } -
TFSWFSXFC(SFFUJOH3FTU$POUSPMMFSLU package kotlinconf.web import kotlinconf.application.GreetingOperations import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestParam import
org.springframework.web.bind.annotation.RestController @RestController class GreetingRestController : GreetingOperations { @GetMapping("/greeting") override fun greeting(@RequestParam("username") username: String): String { return "Hello, $username" } } -
BOESPJE"OESPJE"QQMJDBUJPOLU package kotlinconf import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import
androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.MaterialTheme import androidx.compose.ui.Modifier import kotlinconf.application.RemoteGreetingOperations import kotlinconf.net.http.DefaultClientHttpRequestFactory import kotlinconf.ui.GreetingComposeApp class AndroidApplication : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { GreetingComposeApp( greetingOperations = RemoteGreetingOperations( clientHttpRequestFactory = DefaultClientHttpRequestFactory(), serverHost = "10.0.2.2" ), modifier = Modifier.fillMaxSize() ) } } } } -
EFTLUPQ%FTLUPQ"QQMJDBUJPOLU package kotlinconf import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.Modifier import androidx.compose.ui.window.Window import
androidx.compose.ui.window.application import kotlinconf.application.RemoteGreetingOperations import kotlinconf.net.http.DefaultClientHttpRequestFactory import kotlinconf.ui.GreetingComposeApp fun main() = application { Window(title = "Compose for Desktop", onCloseRequest = ::exitApplication) { GreetingComposeApp( greetingOperations = RemoteGreetingOperations( clientHttpRequestFactory = DefaultClientHttpRequestFactory() ), modifier = Modifier.fillMaxSize() ) } } -
XFC8FC"QQMJDBUJPOLU package kotlinconf import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import
androidx.compose.ui.window.CanvasBasedWindow import kotlinconf.application.RemoteGreetingOperations import kotlinconf.net.http.DefaultClientHttpRequestFactory import kotlinconf.ui.GreetingComposeApp @OptIn(ExperimentalComposeUiApi::class) fun main() { CanvasBasedWindow { GreetingComposeApp( greetingOperations = RemoteGreetingOperations( clientHttpRequestFactory = DefaultClientHttpRequestFactory() ), modifier = Modifier.fillMaxSize() ) } } -
XFCJOEFYIUNM <!DOCTYPE html> <html lang="en"> <head> <title>Compose for Web with
WebAssembly</title> <script src="skiko.js"> </script> <script type="module" src="load.mjs"> </script> </head> <body> <canvas id="ComposeTarget"></canvas> </body> </html> --------------------------------------------------------------------------------------- # load.mjs import { instantiate } from './kotlinconf-is-back-web-wasm.uninstantiated.mjs'; await wasmSetup; instantiate({ skia: Module['asm'] }); -
DPNNPOJPTNBJOJPTLU import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.Modifier import androidx.compose.ui.window.ComposeUIViewController import kotlinconf.application.RemoteGreetingOperations import
kotlinconf.net.http.DefaultClientHttpRequestFactory import kotlinconf.ui.GreetingComposeApp import platform.UIKit.UIViewController fun MainViewController() : UIViewController = ComposeUIViewController { GreetingComposeApp( greetingOperations = RemoteGreetingOperations( clientHttpRequestFactory = DefaultClientHttpRequestFactory(), serverHost = "127.0.0.1" ), modifier = Modifier.fillMaxSize() ) } -
DPNNPOCVJMEHSBEMFLUT plugins { kotlin("multiplatform") kotlin("native.cocoapods") id("com.android.library") id("org.jetbrains.compose") } kotlin {
android() iosX64() iosArm64() iosSimulatorArm64() jvm("desktop") { ... } js(IR) { ... } wasm { ... } cocoapods { version = "1.0.0" summary = "common module for kotlinconf multiplatform apps" homepage = "https://kotlinconf.com/" ios.deploymentTarget = "14.1" podfile = project.file("../ios/Podfile") framework { baseName = "common" isStatic = true } extraSpecAttributes["resources"] = "['src/commonMain/resources/**']" } sourceSets { ... val iosMain by creating ... } } android { ... } -
ଵҊܐ KotlinConf 23 Keynote KotlinConf 2023 A Look at the
Opening Keynote