Kotlin Multiplatform เป็นอนาคตของการพัฒนาข้ามแพลตฟอร์มหรือไม่? เคล็ดลับในการเริ่มต้น

เผยแพร่แล้ว: 2021-01-29

ทุกวันนี้เราสามารถสังเกตแนวโน้มการพัฒนามือถือเพื่อปล่อยแอพได้เร็วยิ่งขึ้น มีความพยายามมากมายที่จะลดเวลาในการพัฒนาโดยการแบ่งปันส่วนรหัสทั่วไประหว่างแพลตฟอร์มต่างๆ เช่น Android และ iOS โซลูชันบางอย่างได้รับความนิยมแล้ว ในขณะที่โซลูชันอื่นๆ ยังอยู่ระหว่างการพัฒนา วันนี้ฉันอยากจะพูดถึงหนึ่งในแนวทางใหม่ล่าสุดจากกลุ่มที่สอง – Kotlin Multiplatform Mobile (KMM เรียกสั้นๆ ว่า)

Kotlin Multiplatform Mobile คืออะไร?

KMM เป็น SDK ที่มีจุดมุ่งหมายเพื่อแบ่งปันตรรกะทางธุรกิจระหว่างแพลตฟอร์มต่างๆ ซึ่งส่วนใหญ่แล้วจะต้องเหมือนกัน สิ่งนี้ทำได้ด้วยชุดคอมไพเลอร์หลายตัวสำหรับโมดูลที่ใช้ร่วมกัน ตัวอย่างเช่น เป้าหมาย Android ใช้ตัวแปร Kotlin/JVM และสำหรับ iOS จะมี Kotlin/Native จากนั้นจึงเพิ่มโมดูลที่ใช้ร่วมกันลงในโปรเจ็กต์แอปพื้นฐานทั่วไป และนักพัฒนาที่รับผิดชอบ UI สามารถมุ่งเน้นไปที่การมอบประสบการณ์ที่ดีที่สุดสำหรับผู้ใช้ในสภาพแวดล้อมที่คุ้นเคย – Android Studio สำหรับ Android และ Xcode สำหรับ iOS

Kotlin Multiplatform กับ Flutter

ปัจจุบัน หนึ่งในโซลูชั่นยอดนิยมสำหรับการพัฒนาแอพข้ามแพลตฟอร์มคือ Flutter โดยเน้นที่กฎ "เขียนแอปเดียวและเรียกใช้ได้ทุกที่" ซึ่งใช้ได้ แต่สำหรับแอปทั่วไปเท่านั้น ในสถานการณ์จริง นักพัฒนามักจะต้องเขียนโค้ดเนทีฟสำหรับแต่ละแพลตฟอร์มเพื่อเติมเต็มช่องว่าง ตัวอย่างเช่น เมื่อปลั๊กอินบางตัวหายไป ด้วยวิธีนี้ แอปจะมีลักษณะเหมือนกันบนแพลตฟอร์มต่างๆ ซึ่งบางครั้งก็เป็นที่ต้องการ แต่ในบางกรณี แอปอาจทำลายแนวทางการออกแบบที่เฉพาะเจาะจงได้

ไอคอนบริการพัฒนาข้ามแพลตฟอร์ม

พร้อมที่จะสร้างแอพของคุณเองหรือยัง

เลือก Flutter

แม้ว่ามันอาจจะฟังดูคล้ายคลึงกัน แต่ Kotlin Multiplatform ไม่ใช่โซลูชันข้ามแพลตฟอร์ม – มันไม่ได้พยายามสร้างวงล้อขึ้นมาใหม่ นักพัฒนายังสามารถใช้เครื่องมือที่พวกเขารู้จักและชื่นชอบได้ มันช่วยลดความยุ่งยากในกระบวนการนำส่วนต่างๆ ของโค้ดกลับมาใช้ใหม่ ซึ่งก่อนหน้านี้ควรจะเขียนขึ้นหลายครั้ง เช่น การส่งคำขอของเครือข่าย การจัดเก็บข้อมูล และตรรกะทางธุรกิจอื่นๆ

ข้อดีและข้อเสียของ Kotlin Multiplatform

ข้อดีของ KMM :

  • แอปที่พัฒนาขึ้นนั้นใช้งานได้จริง 100% สำหรับแต่ละแพลตฟอร์ม – ง่ายต่อการรวมเข้ากับโค้ดที่ใช้อยู่ในปัจจุบันและไลบรารีของบุคคลที่สาม
  • ใช้งานง่าย – นักพัฒนา Android เกือบทั้งหมดใช้ Kotlin อยู่แล้ว ดังนั้นจึงไม่มีความรู้เพิ่มเติมที่จำเป็นสำหรับพวกเขาในการเริ่มต้น
  • สามารถแบ่ง UI สำหรับแต่ละแพลตฟอร์มเป้าหมายได้ – แอปจะรู้สึกสอดคล้องกับระบบนิเวศที่กำหนด
  • ตรรกะที่ใช้ร่วมกัน ช่วยให้นักพัฒนาสามารถเพิ่มคุณสมบัติใหม่และแก้ไขข้อบกพร่องบนระบบปฏิบัติการทั้งสองได้พร้อมกัน

ข้อเสียของ KMM :

  • ส่วนประกอบจำนวนมากยังอยู่ในช่วงอัลฟ่า/เบต้าและอาจไม่เสถียรหรือเปลี่ยนแปลงได้ในอนาคต

บริษัทใดบ้างที่ใช้ KMM?

ตามเว็บไซต์อย่างเป็นทางการ บริษัทต่างๆ ได้รับความสนใจในเทคโนโลยีนี้มากขึ้นเรื่อย ๆ และรายการก็ยาวขึ้นเรื่อย ๆ อย่างต่อเนื่อง ในหมู่พวกเขามีแบรนด์ที่มีชื่อเสียงเช่น Autodesk, VMWare, Netflix หรือ Yandex

จะเริ่มต้นใช้งาน Kotlin Multiplatform ได้อย่างไร?

สถานที่ที่ดีที่สุดในการดำน้ำลึกเพื่อข้อมูลเชิงลึกคือคู่มืออย่างเป็นทางการ แต่ในบทความนี้ ฉันต้องการแสดงตัวอย่างที่ค่อนข้างง่าย แต่น่าสนใจมากกว่าเพียงแค่ "Hello World" ซึ่งจะเรียกและแสดงแอป การ์ตูนล่าสุดโดย Randall Munroe (ลิขสิทธิ์ภายใต้ CC BY-NC 2.5) พร้อมชื่อเรื่องจาก xkcd.com API

คุณสมบัติที่จะครอบคลุม:

  • ตั้งค่าโครงการ
  • เครือข่ายในโมดูลที่ใช้ร่วมกัน
  • UI ที่เรียบง่ายสำหรับทั้ง Android และ iOS

หมายเหตุ: ฉันต้องการให้ตัวอย่างนี้อ่านง่ายสำหรับนักพัฒนาทั้ง Android และ iOS ดังนั้นในบางแห่ง ฉันจึงตั้งใจละเว้นแนวปฏิบัติที่ดีเฉพาะแพลตฟอร์มเพื่อให้ชัดเจนว่าเกิดอะไรขึ้น

ตั้งค่าโครงการ

ขั้นแรก ตรวจสอบให้แน่ใจว่าคุณได้ติดตั้ง Android Studio และ Xcode เวอร์ชันล่าสุด แล้ว เนื่องจากทั้งสองเวอร์ชันจำเป็นสำหรับการสร้างโปรเจ็กต์นี้ จากนั้นใน Android Studio ให้ติดตั้งปลั๊กอิน KMM ปลั๊กอินนี้ช่วยลดความยุ่งยากในหลายๆ อย่าง ในการสร้างโปรเจ็กต์ใหม่ เพียงคลิกสร้างโปรเจ็กต์ใหม่ แล้วเลือกแอปพลิเคชัน KMM

สร้างโครงการ Kotlin Multiplatform Mobile ใหม่

หลังจากสร้างโปรเจ็กต์แล้ว ให้ไปที่ ไฟล์ build.gradle.kts ในไดเร็กทอรีที่แบ่งใช้ ที่นี่คุณต้องระบุการพึ่งพาที่จำเป็นทั้งหมด ในตัวอย่างนี้ เราจะใช้ ktor สำหรับเลเยอร์เครือข่าย kotlinx.serialization สำหรับการแยกวิเคราะห์การตอบสนอง json จากแบ็กเอนด์ และ kotlin coroutines เพื่อทำแบบอะซิงโครนัสทั้งหมด

เพื่อความง่าย ด้านล่างฉันแสดงรายการที่แสดงรายการการพึ่งพาทั้งหมดที่ต้องเพิ่มในรายการที่มีอยู่แล้ว เมื่อคุณเพิ่มการพึ่งพา เพียงแค่ซิงค์โปรเจ็กต์ (พร้อมท์จะปรากฏขึ้น) ขั้นแรก เพิ่มปลั๊กอินการทำให้เป็นอันดับลงในส่วนปลั๊กอิน

 ปลั๊กอิน {
   kotlin("plugin.serialization") เวอร์ชัน "1.4.0"
}

จากนั้นเพิ่มการพึ่งพา

 sourceSets {
   val commonMain โดยรับ {
       การพึ่งพา {
           การใช้งาน ("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
           การใช้งาน ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9-native-mt-2")

           การใช้งาน("io.ktor:ktor-client-core:1.4.1")
           การใช้งาน("io.ktor:ktor-client-json:1.4.1")
           การใช้งาน("io.ktor:ktor-client-serialization:1.4.1")
       }
   }
   val androidMain โดยรับ {
       การพึ่งพา {
           การใช้งาน("io.ktor:ktor-client-android:1.4.1")
       }
   }
   val iosMain โดยรับ {
       การพึ่งพา {
           การใช้งาน("io.ktor:ktor-client-ios:1.4.1")
       }
   }
}

เป็นมูลค่าการกล่าวขวัญว่าในขณะที่เขียนบทความนี้ มีปัญหาบางอย่างกับไลบรารี coroutines เวอร์ชันเสถียรบน iOS นั่นคือสาเหตุที่เวอร์ชันที่ใช้มีส่วนต่อท้าย native-mt-2 (ซึ่งย่อมาจาก native multithreading) คุณสามารถตรวจสอบสถานะปัจจุบันของปัญหานี้ได้ที่นี่

เครือข่ายในโมดูลที่ใช้ร่วมกัน

อันดับแรก เราต้องการคลาสที่แสดงการตอบกลับ – ฟิลด์เหล่านั้นมีอยู่ใน json ที่ส่งคืนโดยแบ็กเอนด์

 นำเข้า kotlinx.serialization.Serializable

@ซีเรียลไลซ์ได้
คลาสข้อมูล XkcdResponse(
   วาล img: สตริง,
   ชื่อวาล: สตริง,
   วันวาล: Int,
   เดือนวาล: Int,
   ปีวาล: Int,
)

ต่อไปเราต้อง สร้างคลาสที่แสดง API ด้วยไคลเอนต์ HTTP ในกรณีที่เราไม่ได้ระบุฟิลด์ทั้งหมดที่มีอยู่ใน json เราสามารถใช้คุณสมบัติ ignoreUnknownKeys เพื่อให้ซีเรียลไลเซอร์สามารถละเว้นฟิลด์ที่ขาดหายไปได้ ตัวอย่างนี้มีจุดสิ้นสุดเพียงจุดเดียวที่แสดงโดยฟังก์ชันที่ระงับ โมดิฟายเออร์นี้บอกคอมไพเลอร์ว่าฟังก์ชันนี้เป็นแบบอะซิงโครนัส ฉันจะอธิบายเพิ่มเติมด้วยโค้ดเฉพาะแพลตฟอร์ม

 นำเข้า io.ktor.client.*
นำเข้า io.ktor.client.features.json.*
นำเข้า io.ktor.client.features.json.serializer*
นำเข้า io.ktor.client.request.*

คลาส XkcdApi {
   val baseUrl ส่วนตัว = "https://xkcd.com"

   วาลส่วนตัว httpClient = HttpClient () {
       ติดตั้ง (JsonFeature) {
           serializer = KotlinxSerializer (
               kotlinx.serialization.json.Json {
                   ละเว้นUnknownKeys = true
               }
           )
       }
   }

   ระงับ fun fetchLatestComic() =
       httpClient.get<XkcdResponse>("$baseUrl/info.0.json")

}

เมื่อเลเยอร์เครือข่ายของเราพร้อม เราสามารถย้ายไปยังเลเยอร์ของโดเมนและสร้างคลาสที่แสดงโมเดลข้อมูลในเครื่องได้ ในตัวอย่างนี้ ฉันข้ามฟิลด์อื่นๆ และเหลือเพียงชื่อการ์ตูนและ URL ของรูปภาพ

 คลาสข้อมูล ComicModel(
   val imageUrl: สตริง,
   ชื่อวาล: String
)

ส่วนสุดท้ายสำหรับเลเยอร์นี้คือการสร้างกรณีการใช้งานซึ่งจะเรียกใช้คำขอเครือข่ายแล้วแมปการตอบสนองต่อโมเดลในเครื่อง

 คลาส GetLatestComicUseCase (val ส่วนตัว xkcdApi: XkcdApi) {
   ระงับ fun run() = xkcdApi.fetchLatestComic()
       .let { ComicModel(it.img, it.title) }
}

Simple UI สำหรับ Android

ถึงเวลา ย้ายไปยัง ไดเร็กทอรี androidApp - นี่คือที่จัดเก็บแอพ Android ดั้งเดิม ขั้นแรก เราต้อง เพิ่มการพึ่งพาเฉพาะของ Android ลงในไฟล์ build.gradle.kts อื่น ๆ ที่อยู่ที่นี่ อีกครั้ง รายการด้านล่างแสดงเฉพาะการขึ้นต่อกันที่ควรเพิ่มไปยังรายการที่มีอยู่แล้ว แอปนี้จะใช้สถาปัตยกรรม Model-View-ViewModel (สองบรรทัดแรก) และ Glide เพื่อโหลดภาพการ์ตูนจาก URL ที่ส่งคืน (สองบรรทัดที่สอง)

 การพึ่งพา {
   การใช้งาน ("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
   การใช้งาน ("androidx.lifecycle:lifecycle-livedata-ktx:2.2.0")
   การใช้งาน("com.github.bumptech.glide:glide:4.11.0")
   annotationProcessor("com.github.bumptech.glide:compiler:4.11.0")
}

ตามค่าเริ่มต้น โปรเจ็กต์ที่สร้างขึ้นใหม่ควรมี MainActivity และไฟล์โครงร่าง activity_main.xml มาเพิ่มมุมมองกันเถอะ – หนึ่ง TextView สำหรับชื่อและหนึ่ง ImageView สำหรับการ์ตูนเอง

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andro
   หุ่นยนต์:
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:gravity="ศูนย์"
   android:orientation="vertical">

   <มุมมองข้อความ
       หุ่นยนต์:
       android:layout_width="wrap_content"
       android:layout_height="wrap_content”/>

   <ImageView
       หุ่นยนต์:
       android:layout_width="match_parent"
       android:layout_height="wrap_content"/>

</LinearLayout>

จากนั้น เราจำเป็นต้องแสดงสถานะของแอป อาจเป็นได้ทั้งการโหลดการ์ตูนใหม่ แสดง หรือเราอาจพบข้อผิดพลาดขณะโหลด

 สถานะคลาสปิดผนึก {
   กำลังโหลดวัตถุ : State()
   class Success(val ผลลัพธ์: ComicModel) : State()
   ข้อผิดพลาดของวัตถุ: สถานะ ()
}

ตอนนี้ มา เพิ่ม ViewModel ขั้นต่ำโดยใช้ตรรกะทางธุรกิจที่สร้างไว้ก่อนหน้านี้ นำเข้าได้ทุกคลาส MutableLiveData เป็นฟิลด์ที่สังเกตได้ – มุมมองจะสังเกตการเปลี่ยนแปลงที่เกิดขึ้นและอัปเดตตัวเองตามนั้น viewModelScope เป็นขอบเขต coroutine ที่เชื่อมโยงกับวงจรชีวิตของ viewmodel - ในกรณีที่ปิดแอป มันจะยกเลิกงานที่ค้างอยู่โดยอัตโนมัติ

 คลาส MainViewModel : ViewModel () {
   วาลส่วนตัว getLatestComicUseCase = GetLatestComicUseCase(XkcdApi())
   วาลการ์ตูน = MutableLiveData<State<ComicModel>>()

   สนุก fetchComic() {
       viewModelScope.launch {
           comic.value = สถานะกำลังโหลด ()
           runCatching { getLatestComicUseCase.run() }
               .onSuccess { comic.value = State.Success (มัน) }
               .onFailure { comic.value = State.Error () }
       }
   }
}

สิ่งสุดท้าย – MainActivity เพื่อเชื่อมต่อทุกอย่าง

 คลาส MainActivity : AppCompatActivity (R.layout.activity_main) {
   val viewModel ส่วนตัว: MainViewModel โดย lazy {
      ViewModelProvider(this).get(MainViewModel::class.java)
   }

   แทนที่ความสนุกบนสร้าง (savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      viewModel.comic.observe (นี้) {
         เมื่อ (มัน) {
            คือ State.Loading -> {
               findViewById<TextView>(R.id.titleLabel).text = "กำลังโหลด"
            }
            คือสถานะความสำเร็จ -> {
               findViewById<TextView>(R.id.titleLabel).text = it.result.title
               ร่อนด้วย (นี้)
                  .load(it.result.img)
                  .into(findViewById(R.id.image))
            }
            คือ State.Error -> {
               findViewById<TextView>(R.id.titleLabel).text = "ข้อผิดพลาด"
            }
         }
      }
      viewModel.fetchComic()
   }
}

เพียงเท่านี้ แอป Android ก็พร้อมแล้ว!

แอปพลิเคชัน Android ที่พัฒนาด้วย KMM

UI อย่างง่ายสำหรับ iOS

ทุกอย่างข้างต้นทำใน Android Studio ดังนั้นสำหรับส่วนนี้ เรามาเปลี่ยนเป็น Xcode เพื่อให้สะดวกยิ่งขึ้น ในการดำเนินการนี้ เพียงแค่ เปิด Xcode และเลือกไดเร็กทอรี iosApp – มันมีโปรเจ็กต์ Xcode ที่กำหนดค่าไว้ล่วงหน้า โดยค่าเริ่มต้น โปรเจ็กต์นี้ใช้ SwiftUI สำหรับ GUI ดังนั้นให้ยึดตามนั้นเพื่อความเรียบง่าย

สิ่งแรกที่ต้องทำคือสร้างตรรกะพื้นฐานเพื่อดึงข้อมูลการ์ตูน เหมือนเมื่อก่อน เราต้องการบางสิ่งเพื่อเป็นตัวแทนของรัฐ

 สถานะ enum {
    กรณีโหลด
    กรณีสำเร็จ (ComicModel)
    กรณีผิดพลาด
}

ต่อไป มาเตรียม ViewModel กันอีกครั้ง

 คลาส ViewModel: ObservableObject {
    ให้ getLatesteComicUseCase = GetLatestComicUseCase(xkcdApi: XkcdApi())
        
    @Published var comic = State.loading
        
    ในนั้น() {
        self.comic = .loading
        getLatestComicUseCase.run { fetchedComic ข้อผิดพลาดใน
            if fetchedComic != ไม่มี {
                self.comic = .success (เรียกการ์ตูน!)
            } อื่น {
                self.comic = .error
            }
        }
    }
}

และสุดท้าย มุมมอง

หมายเหตุ: เพื่อความเรียบง่าย ฉันใช้คอมโพเนนต์ SwiftUI RemoteImage เพื่อแสดงรูปภาพ เหมือนกับที่ฉันใช้ Glide บน Android

 โครงสร้าง ContentView: ดู {
 
    @ObservedObject ส่วนตัว (ชุด) var viewModel: ViewModel
    
    var body: บางมุมมอง {
        การ์ตูนวิว()
    }
    --
    func ส่วนตัว comicView () -> บางมุมมอง {
        สลับ viewModel.comic {
        กรณี .loading:
            ส่งคืน AnyView(ข้อความ("กำลังโหลด"))
        case .result(ให้การ์ตูน):
            ส่งคืน AnyView (VStack {
                ข้อความ(comic.title)
                RemoteImage (url: comic.img)
            })
        กรณี .ข้อผิดพลาด:
            ส่งคืน AnyView(Text("Error"))
        }
    }
}

เพียงเท่านี้ แอป iOS ก็พร้อมแล้ว!

แอปพลิเคชัน iOS ที่พัฒนาด้วย KMM

สรุป

สุดท้ายนี้ เพื่อตอบคำถามจากหัวข้อ – Kotlin Multiplatform คืออนาคตของการพัฒนาข้ามแพลตฟอร์มหรือไม่? - ทุกอย่างขึ้นอยู่กับความต้องการ หากคุณต้องการสร้างแอปขนาดเล็กที่เหมือนกันสำหรับทั้งสองแพลตฟอร์มมือถือพร้อมกัน ก็คงไม่ใช่ เพราะคุณจำเป็นต้องมีความรู้ที่จำเป็นในการพัฒนาสำหรับทั้งสองแพลตฟอร์ม

ปล่อยไอคอนผลิตภัณฑ์

พัฒนาแอพตัวต่อไปของคุณกับผู้เชี่ยวชาญของเรา

ได้รับใบเสนอราคา

อย่างไรก็ตาม หากคุณมีทีมนักพัฒนา Android และ iOS อยู่แล้ว และต้องการมอบประสบการณ์ผู้ใช้ที่ดีที่สุด ก็สามารถลดเวลาในการพัฒนาลง ได้อย่างมาก เช่นเดียวกับในตัวอย่างที่ให้มา ต้องขอบคุณโมดูลที่ใช้ร่วมกัน ตรรกะของแอปพลิเคชันถูกใช้งานเพียงครั้งเดียวและส่วนต่อประสานผู้ใช้ถูกสร้างขึ้นในลักษณะเฉพาะของแพลตฟอร์มอย่างสมบูรณ์ ทำไมไม่ลองดูล่ะ? อย่างที่คุณเห็น มันง่ายที่จะเริ่มต้น

อยากรู้เกี่ยวกับการพัฒนาข้ามแพลตฟอร์มจากมุมมองทางธุรกิจหรือไม่? อ่านบทความของเราเกี่ยวกับข้อดีของการพัฒนาข้ามแพลตฟอร์ม