Quick Preview Compose Multiplatform in Our Existing Project

Sinj Menarucho
KBTG Life
Published in
5 min readAug 2, 2023

สิ่งที่ควรรู้: Compose Multiplatform for iOS ยังอยู่ในช่วง Alpha หมายความว่าอาจมีการเปลี่ยนแปลงได้ตลอดเวลา สิ่งที่เขียนอยู่นี้อาจใช้ไม่ได้โดยไม่รู้ตัว!!

- Compose Multiplatform = CM
- Kotlin Multiplatform Mobile = KMM
- Native = iOS / Android
- Proof of Concept = POC

Photo by Louis Tsai on Unsplash

เกริ่นนำ

ส่วนตัวผมรับผิดชอบโปรเจคพัฒนา Mobile Application ด้วยภาษา Native แต่มีความสนใจในการทำ Multiplatform อยู่เป็นทุนเดิม ไม่ว่าจะเป็น Flutter หรือ ​Kotlin Multiplatform Mobile (KMM)

โดยมีเป้าหมายเหมือนคนส่วนใหญ่ คือ เขียนครั้งเดียว ใช้ได้ทุกแพลตฟอร์ม
เพื่อลดความผิดพลาดและลดเวลาในการพัฒนา

หากเราเคยลองเล่น KMM มาก่อน จะรู้ว่าเราสามารถทำ Shared Library เพื่อแชร์ Logic, Variable, Enum และ Class ต่างๆ เพื่อใช้งานร่วมกันระหว่าง iOS / Android ได้ แต่…

KMM ไม่สามารถทำ Shared UI ขึ้นมาใช้ร่วมได้ระหว่าง iOS / Android

ซึ่งนับว่าเป็นเรื่องน่าดีใจอย่างยิ่งที่ล่าสุดในงาน Kotlin Conf 2023 (จัดขึ้นเมื่อวันที่ 18 Apr 2023) ได้มีการประกาศว่า Compose Multiplatform (CM) ได้ปรับสถานะให้ Compose for iOS มาเป็น Alpha Version นั่นหมายความว่า UI ที่เขียนด้วย Jetpack Compose เพื่อใช้บน Android จะนำมาใช้บน iOS ได้ด้วย ว้าว

Proof of Concept (POC)

โอเค เราพอรู้กันเบื้องต้นแล้วว่า CM จะเข้ามาเพิ่มความสามารถให้ KMM ในการแชร์ UI กันข้ามแพลตฟอร์ม

ทีนี้คำถามของเราคือ มันใช้ได้จริงไหม? น่าใช้แค่ไหน? เอามาใช้กับโปรเจค Native ที่เรามีอยู่ได้เลยไหม? โดยเน้นไปที่การใช้งานบน iOS เป็นหลัก เพราะอย่างที่รู้กันว่า Jetpack Compose เป็นที่ยอมรับอยู่แล้วในการพัฒนา Android

โดย Scope ของการทำ POC คือ

  1. พิสูจน์ว่าเราสามารถนำ Jetpack Compose UI ที่มีใน Existing Project มาเปลี่ยนเป็น CM แล้วจะแสดงผลได้เหมือนกัน ทั้ง iOS และ Android
  2. Shared Resource และ Common UI Class สามารถใช้งานร่วมกันได้ทั้ง 2 แพลตฟอร์ม (Common UI Class เช่น Fonts, Colors, Shape, UI Component)

เตรียมตัวก่อนทำ POC

อย่างแรกคือต้องติดตั้ง Android Studio Plugins -> Kotlin Multiplatform Mobile

เริ่ม POC (สักที)

ถึงคราวเริ่มลงมือกันจริงๆ เราเริ่มต้นด้วยการเปิด Android Studio ขึ้นมา และเลือก New Project -> Kotlin Multi Platform

จากประสบการณ์ที่ผมเคยลองทำ KM มาก่อน เรามีทางลัดที่รวดเร็วกว่าการเริ่มใหม่จากศูนย์ นั่นคือเราสามารถโคลน Template มาใช้ได้เลยจาก
JetBrain
ซึ่งเหมาะกับผม เพราะผมเน้นการไปที่ทำ POC เท่านั้นครับ

แต่ช้าก่อน!! หลังจากที่ได้ลองใช้ Jetbrain’s Template แล้ว เราขอแนะนำให้ใช้ MOKO’s Template ซึ่ง Forked มาจาก JetBrain แทน

ทำไมถึงต้อง MOKO’s Template

ต้องบอกว่า KMM และ CM ยังมีปัญหาเรื่องการใช้งานอยู่เล็กน้อยเกี่ยวกับ Resource คือเราไม่สามารถลากไฟล์ Image หรือ Fonts ลง Drawable Folder ได้เหมือนการเขียน Kotlin Application ปกติ ต้องมีขั้นตอนเพิ่มเติมเล็กน้อย เพื่อให้สามารถใช้งาน Resource เหล่านั้นได้

ปวดหัวมาพักใหญ่ จนในที่สุดผมก็ค้นพบว่ามีคนทำ Plugin เพื่อช่วยแก้ปัญหาเรื่องนี้แล้ว นั่นก็คือ moko-resources ซึ่งใน MOKO’s Template ได้มีการติดตั้ง Plugin ดังกล่าวไว้ให้เรียบร้อยครับ หมดปัญหาพร้อมไปต่อ

Project Structure

หลังจากที่โคลนเสร็จ เรามาดูโครงสร้างโปรเจคกันสักเล็กน้อย

  • shared/commonMain โฟลเดอร์หลักสำหรับเก็บ Kotlin Code สำหรับการทำ Compose Multiplatform
  • shared/iOSMain , androidMain โฟลเดอร์สำหรับในกรณีที่เราต้องการให้มีการแสดงผลที่แตกต่างกันระหว่าง 2 แพลตฟอร์ม
Folder ที่เราจะโฟกัสเป็นหลัก คือ shared/commonMain
shared/commonMain คือโฟลเดอร์ที่เราจะใช้เป็นหลัก

commonMain

ภายในโฟลเดอร์ commonMain จะมีโฟลเดอร์ย่อยอยู่อีก 2 โฟลเดอร์ คือ

  • Kotlin ใช้สำหรับเก็บ Source Code Kotlin (*.kt)
    Remark: โฟลเดอร์ “KP” ผมสร้างขึ้นมาเอง เพื่อเก็บ Existing Code ที่ผมนำมาใช้
    * files อื่นๆ นอกจาก App.kt เป็น Sample File ในเรื่องต่างๆ ใครไม่ได้ใช้งานสามารถลบได้นะครับ
  • Resources ใช้สำหรับเก็บ Images, File, Fonts และ Colors ต่างๆ

Same Code, Same UI

จากเป้าหมายของการ POC เราต้องการพิสูจน์ว่าโค้ด (Kotlin) เดียวแสดงผลบน iOS และ Android ได้เหมือนกัน เราจึงตั้งต้นจากโค้ด Kotlin ที่มีอยู่แล้ว โดยตั้งสมมติฐานว่าเมื่อเรานำมาแปลง CM การแสดงผลจะเหมือนเดิมบน iOS ผมจึงทำการเลือก Screen มา 1 หน้าเพื่อทำการทดสอบ รวมถึงหยิบเอา UI Component, Common UI Class มาด้วยดังนี้ โดยทำการลากไฟล์เหล่านี้มาใส่ใน CM Project เลย

เมื่อเราได้หนูทดลองแล้ว ให้ทำการเพิ่ม Screen (CardMain.kt) ที่เราเลือกไว้ใน main.ios.kt ด้วย เมื่อเราทำการ Export ออกมา ฝั่ง iOS ก็จะสามารถเลือกหน้านี้ไปใช้ได้

fun CardViewController() = ComposeUIViewController { CardMain() }
// เวลาเรียกใช้ บน iOS จะเรียกเป็น CardViewController แทน CardMain

หลังจากที่เตรียมการเบื้องต้นเสร็จ สิ่งที่ผมเจอต่อมาคือ Common UI ที่ดึงเข้ามาใน CM Project ใช้งานไม่ได้ พอกดเข้าไปดู ก็พบว่าเป็นเกิดจากการเรียกใช้ Resource ไม่ได้ สำหรับวิธีแก้ไข ผมเขียนไว้ให้ด้านล่างนี้ครับ

Drawable ที่หายไป ?

โปรเจค Kotlin / Compose Multiplatform นั้นมีความต่างกับ Android Project ทั่วไปตรงที่ไม่มี Drawable Folder มาให้ ทำให้ไม่สามารถเรียกใช้ Resource ได้แบบปกติ เช่น R.font.XXXX , R.image.XXXX ที่ต้องมีการแก้ไขโค้ดเล็กน้อย

Fonts


//Before
DEFAULT_FONT_FAMILY = FontFamily(
Font(R.font.<fontname>_normal, weight = FontWeight.Normal)
)
//After
DEFAULT_FONT_FAMILY() = fontFamilyResource(MR.fonts.<fontname>_normal.<fontname>_normal)

Images, Icons

//before
painterResource(id = R.drawable.ic_home_card_circle_back_arrow)
//After
painterResource(MR.images.ic_home_card_circle_back_arrow)

Ready to Build

ถือว่าเราโชคดีมาก เพราะหลังจากแก้ปัญหาเรื่อง Resource โปรเจคก็สามารถทำการทดสอบ Build + Run ได้เลย โดยวิธีการนั้นดูได้จากลิงก์ข้างล่างนี้ครับ

Compose Multiplatform บน iOS existing project

พอ Build ได้แล้ว ขั้นต่อมาก็คือการนำ CM ที่สร้างขึ้นไปปลั๊กกับ iOS Project ที่เรามี โดย ณ ตอนที่เราทำ POC มีอยู่ 2 วิธีที่จะทำได้ นั่นคือ…

  1. CocoaPods
  2. Connect Framework into Xcode Project

ผมเลือกใช้วิธีที่ 2 คือการทำ Link Framework เข้าไปโดยตรง ขั้นตอนจะเป็นดังนี้

1. เปิด XCode แล้วกดที่ Project -> Targets -> Build phases -> “+” -> New Run Script Phase

2. เพิ่ม Script

cd "$SRCROOT/<Path_to_KMM>/"

: ${EXPANDED_CODE_SIGN_IDENTITY:=-}
./gradlew :shared:embedAndSignAppleFrameworkForXcode
#./gradlew :shared:packForXcode) - old style KMM

3. ไปที่ Build Settings -> Framework Search Paths

4. เพิ่ม Path

$(SRCROOT)/<Path_to_kmm>/shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)

5. ใน Build Setting ค้นหา Other Linker Flags เพิ่ม Flag

-framework shared

6. ลอง Run ดูสักครั้ง เพื่อความสบายใจ ก่อนที่เราจะลองเรียกใช้ Shared Framework ในโปรเจคเราต่อไป

Shared Framework

หลังจากที่เราทำตามสเต็ปข้างบนแล้ว ถ้าไม่มีอะไรผิดผลาด เราก็จะได้ Output ออกมาเป็น Framework ที่ชื่อว่า shared.framework (ชื่อสามารถเปลี่ยนได้นะครับ)

ลองใช้ Compose Multiplatform (Shared Framework)

ถึงขั้นตอนนี้ เราจะมาลองเปิด Compose UI บน iOS กันแล้วนะครับ

1. ทำการ Import Shared

import shared

2. เปิดหน้า Screen ใหม่

let module = Main_iosKt.CardViewController()
self.viewController?
.navigationController?
.pushViewController(module, animated: true)

3. ชมผลงาน Enjoy ครับผม!

ซ้าย (iOS) // ขวา (Android)

Compose Multiplatform vs Flutter

ออกตัวก่อนว่าผมจะขอเทียบกับการใช้งานร่วมกับ Existing Project (Native) เท่านั้นนะครับ

  • ในเรื่องของการ Integrate กับ Existing Project ยากง่ายคนละแบบ โดย Flutter จะง่ายกว่าตรงที่ไม่ต้องเพิ่ม Run Script แต่ต้องเรียกใช้งานผ่าน FlutterEngine ซึ่งค่อนข้างยุ่งยาก ส่วน Compose เรียกใช้สะดวกกว่า เพียงแค่ Import Shared เข้าไปเท่านั้น
  • Flutter Document ดีกว่า อาจเป็นเพราะมีมาก่อนสักพัก ส่วน CM เพิ่งเป็น Alpha Release
  • CM เวลาติดปัญหา หาทางแก้ไขได้ยาก Community ยังไม่แข็งแรงเท่า Flutter
  • CM เหมาะกับการทำ Modular มากกว่า เพราะแยกฟีเจอร์ออกมาเป็นหลาย Module ได้ ส่วนทางฝั่ง Flutter ถ้านำมา Run บน iOS ต้อง Run ผ่าน FlutterEngine (ถ้ามีวิธีทำให้ Run แยกได้ก็ขออภัยนะครับ)

TL;DR

สุดท้าย ถ้าข้างบนดูยาวไป ผมขอสรุปไว้ตามนี้ครับ

จากการลองเล่น Compose Multiplatform โดยเน้นการนำมาใช้กับ Existing Project

  1. สำหรับโปรเจคที่พัฒนาด้วย Native (Kotlin/Swift) น่าสนใจมากครับ เพราะอาจจะลดเวลาพัฒนาลงไปได้เกือบครึ่ง โดยการพัฒนาบาง Screen/Flow/Feature ด้วยภาษา Kotlin แล้วนำมาใช้บน iOS
  2. ถ้านำ Compose Multiplatform มาใช้ จะมี Learning Curve น้อยกว่าการนำ Flutter มาใช้ เพราะต้องเรียนรู้ภาษา Dart เพิ่มอีก 1 ภาษา
  3. การ Maintain และการทำ CI/CD ยังดูงงๆ ยังไม่มี Best Practice
  4. ส่วนตัวผมยังติดปัญหาไม่สามารถ Run ลงบน Device จริงได้ T-T และคงไม่หาทางแก้แล้ว ไว้เป็น Beta แล้วค่อยลองกันอีกที
  5. UX บางอย่างยังต้องรอการปรับปรุง เช่น การ Transition จาก Native ไป CM จะเห็นหน้าจอขาวๆ แว่บนึงครับ หรือ UI Animation บางอย่างที่ฝั่ง iOS ปกติจะไม่มี เช่น Ripple Effect บนปุ่มต่างๆ
  6. ยังไม่น่าใช้ (ในตอนนี้) เนื่องจากคนยังใช้งานน้อยมาก ถ้าคุณติดปัญหาอะไร ค่อนข้างยากในการหาคำตอบ แต่ในอนาคตคิดว่าเป็นอีกตัวเลือกที่น่าสนใจครับ

Reference

สำหรับใครที่ชื่นชอบบทความนี้ อย่าลืมกดติดตาม Medium: KBTG Life เรามีสาระความรู้และเรื่องราวดีๆ จากชาว KBTG พร้อมเสิร์ฟให้ที่นี่ที่แรก

--

--