Kotlin Multiplatform 是跨平台開發的未來嗎? 關於如何開始的提示

已發表: 2021-01-29

如今,我們可以觀察到移動開發的趨勢是更快地發布應用程序。 有許多嘗試通過在不同平台(如 Android 和 iOS)之間共享通用代碼部分來減少開發時間。 一些解決方案已經流行起來,而其他解決方案仍在開發中。 今天,我想討論第二組中的一種最新方法——Kotlin Multiplatform Mobile(簡稱 KMM)。

什麼是 Kotlin 多平台移動設備?

KMM 是一個 SDK,主要旨在在平台之間共享業務邏輯——在大多數情況下,這部分無論如何都必須相同。 這要歸功於共享模塊的一組多個編譯器。 例如,Android 目標使用 Kotlin/JVM 變體,而 iOS 則使用 Kotlin/Native 變體。 然後可以將共享模塊添加到典型的原生應用程序項目中,負責 UI 的開發人員可以專注於在他們熟悉的環境中為用戶提供最佳體驗——Android Studio for Android 和 Xcode for iOS。

Kotlin 多平台與 Flutter

目前,跨平台應用程序開發最流行的解決方案之一是 Flutter。 它專注於“編寫一個應用程序並在任何地方運行它”的規則——它有效,但僅適用於簡單的應用程序。 在實際情況下,開發人員通常不得不為每個平台編寫原生代碼來填補空白,例如,當缺少某些插件時。 使用這種方法,應用程序在不同平台上看起來相同,這有時是可取的,但在某些情況下,它可能會違反特定的設計準則。

跨平台開發服務圖標

準備好構建自己的應用了嗎?

選擇顫振

雖然聽起來很相似,但 Kotlin Multiplatform 並不是一個跨平台的解決方案——它不會試圖重新發明輪子。 開發人員仍然可以使用他們熟悉和喜歡的工具。 它只是簡化了重用以前應該多次編寫的部分代碼的過程,例如發出網絡請求、存儲數據和其他業務邏輯。

Kotlin 多平台的優缺點

KMM 的優點

  • 開發的應用程序對於每個平台都是 100% 原生的- 很容易與當前使用的代碼和第三方庫集成
  • 易於使用——幾乎所有的 Android 開發者都已經在使用 Kotlin,因此他們幾乎不需要額外的知識就可以開始使用
  • UI 可以針對每個目標平台進行拆分——該應用程序將與任何給定的生態系統保持一致
  • 共享邏輯允許開發人員同時在兩個操作系統上添加新功能並修復錯誤

KMM 的缺點

  • 許多組件仍處於 Alpha/Beta 階段,未來可能會不穩定或發生變化

哪些公司使用 KMM?

根據官方網站,公司對這項技術的興趣越來越大,而且名單越來越長。 其中,有 Autodesk、VMWare、Netflix 或 Yandex 等知名品牌。

如何開始使用 Kotlin 多平台?

深入了解信息的最佳位置是官方指南,但在本文中,我想展示一個相當簡單但比“Hello World”更有趣的示例,即應用程序獲取和顯示Randall Munroe 的最新漫畫(根據 CC BY-NC 2.5 許可),其標題來自 xkcd.com API。

要涵蓋的功能:

  • 項目設置
  • 共享模塊中的網絡
  • 適用於 Android 和 iOS 的簡單 UI

注意:我希望這個示例對 Android 和 iOS 開發人員都一樣容易閱讀,所以在某些地方我故意省略了一些特定於平台的良好實踐,只是為了清楚發生了什麼

項目設置

首先,確保您安裝了最新版本的 Android Studio 和 Xcode ,因為它們都是構建此項目所必需的。 然後,在 Android Studio 中,安裝 KMM 插件。 這個插件簡化了很多事情——要創建一個新項目,只需單擊 Create New Project 並選擇 KMM Application。

創建一個新的 Kotlin Multiplatform Mobile 項目

創建項目後,導航到共享目錄中的build.gradle.kts文件。 在這裡,您必須指定所有必需的依賴項。 在這個例子中,我們將使用 ktor 作為網絡層,使用kotlinx.serialization來解析來自後端的 json 響應,並使用 kotlin 協程來異步完成這一切。

為簡單起見,下面我提供的清單顯示了必須添加到已經存在的依賴項的所有依賴項。 添加依賴項時,只需同步項目即可(會出現提示)。 首先,將序列化插件添加到插件部分。

 插件{
   kotlin("plugin.serialization") 版本 "1.4.0"
}

然後添加依賴項。

 源集{
   val commonMain 通過獲取 {
       依賴{
           實施(“org.jetbrains.kotlinx:kotlinx-序列化-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”)
       }
   }
}

值得一提的是,在撰寫本文時,iOS 上的協程庫的穩定版本存在一些問題——這就是為什麼使用的版本有 native-mt-2 後綴(代表原生多線程)的原因。 您可以在此處查看此問題的當前狀態。

共享模塊中的網絡

首先,我們需要一個表示響應的類——這些字段存在於後端返回的 json 中。

 導入 kotlinx.serialization.Serializable

@Serializable
數據類 XkcdResponse(
   val img:字符串,
   val 標題:字符串,
   驗證日:詮釋,
   val月份:整數,
   值年:整數,
)

接下來,我們需要使用 HTTP 客戶端創建一個表示 API 的類。 如果我們沒有提供 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"

   私人 val httpClient = HttpClient() {
       安裝(JsonFeature){
           序列化器 = KotlinxSerializer(
               kotlinx.serialization.json.Json {
                   忽略未知鍵=真
               }
           )
       }
   }

   暫停樂趣 fetchLatestComic() =
       httpClient.get<XkcdResponse>("$baseUrl/info.0.json")

}

當我們的網絡層準備好時,我們可以移動到領域層並創建一個代表本地數據模型的類。 在這個例子中,我跳過了更多的字段,只留下了漫畫標題和圖片的 URL。

 數據類 ComicModel(
   val imageUrl:字符串,
   val 標題:字符串
)

這一層的最後一部分是創建一個用例,該用例將觸發網絡請求,然後將響應映射到本地模型。

 類GetLatestComicUseCase(私人val xkcdApi:XkcdApi){
   暫停樂趣 run() = xkcdApi.fetchLatestComic()
       .let { ComicModel(it.img, it.title) }
}

簡單的安卓用戶界面

是時候移動到androidApp目錄了——這是存儲原生 Android 應用程序的地方。 首先,我們需要向位於此處的另一個build.gradle.kts文件添加一些特定於 Android 的依賴項。 同樣,下面的清單只顯示了應該添加到已經存在的依賴項中的依賴項。 這個應用程序將使用 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 版本="1.0" 編碼="utf-8"?>
<線性佈局 xmlns:andro
   安卓:
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   機器人:重力=“中心”
   安卓:方向=“垂直”>

   <文本視圖
       安卓:
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>

   <圖像視圖
       安卓:
       android:layout_width="match_parent"
       android:layout_height="wrap_content"/>

</線性佈局>

然後我們需要一些應用程序狀態的表示- 它可以是加載新漫畫,顯示它,或者我們可能在加載時遇到錯誤。

 密封類狀態{
   對象加載:狀態()
   類成功(驗證結果:ComicModel):狀態()
   對象錯誤:狀態()
}

現在讓我們使用之前創建的業務邏輯添加一個最小的 ViewModel 。 可以導入所有類。 MutableLiveData是一個可觀察的字段——視圖將觀察它的變化並相應地更新自己。 viewModelScope是一個與 viewmodel 的生命週期相關聯的協程作用域——如果應用程序關閉,它將自動取消掛起的任務。

 類 MainViewModel : ViewModel() {
   私人 val getLatestComicUseCase = GetLatestComicUseCase(XkcdApi())
   val 漫畫 = MutableLiveData<State<ComicModel>>()

   有趣的 fetchComic() {
       viewModelScope.launch {
           Comic.value = State.Loading()
           runCatching { getLatestComicUseCase.run() }
               .onSuccess { Comic.value = State.Success(it) }
               .onFailure { Comic.value = State.Error() }
       }
   }
}

最後一件事 – MainActivity 將所有內容連接起來。

 類 MainActivity : AppCompatActivity(R.layout.activity_main) {
   private val viewModel: MainViewModel bylazy {
      ViewModelProvider(this).get(MainViewModel::class.java)
   }

   覆蓋 fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      viewModel.comic.observe(這個) {
         當(它){
            是 State.Loading -> {
               findViewById<TextView>(R.id.titleLabel).text = "加載中"
            }
            是 State.Success -> {
               findViewById<TextView>(R.id.titleLabel).text = it.result.title
               Glide.with(this)
                  .load(it.result.img)
                  .into(findViewById(R.id.image))
            }
            是 State.Error -> {
               findViewById<TextView>(R.id.titleLabel).text = "錯誤"
            }
         }
      }
      viewModel.fetchComic()
   }
}

就是這樣,Android 應用程序已準備就緒!

使用 KMM 開發的 Android 應用程序

iOS的簡單用戶界面

上面的一切都是在 Android Studio 中完成的,所以對於這一部分,讓我們切換到 Xcode 以使其更方便。 為此,只需打開 Xcode 並選擇 iosApp 目錄——它包含一個預配置的 Xcode 項目。 默認情況下,該項目使用 SwiftUI 作為 GUI,所以為了簡單起見,讓我們堅持使用它。

首先要做的是創建基本邏輯來獲取漫畫數據。 就像以前一樣,我們需要一些東西來表示狀態。

 枚舉狀態{
    裝箱
    案例成功(漫畫模型)
    案例錯誤
}

接下來,讓我們再次準備一個 ViewModel

 類 ViewModel:ObservableObject {
    讓 getLatestComicUseCase = GetLatestComicUseCase(xkcdApi: XkcdApi())
        
    @Published var Comic = State.loading
        
    在裡面() {
        self.comic = .loading
        getLatestComicUseCase.run { fetchedComic,錯誤
            如果 fetchedComic != nil {
                self.comic = .success(fetchedComic!)
            } 別的 {
                self.comic = .error
            }
        }
    }
}

最後,觀點。

注意:為簡單起見,我使用 SwiftUI 組件 RemoteImage 來顯示圖像,就像我在 Android 上使用 Glide 一樣。

 結構內容視圖:查看{
 
    @ObservedObject private(set) var viewModel: ViewModel
    
    var body: 一些視圖 {
        漫畫視圖()
    }
    --
    私人 func ComicView() -> 一些視圖 {
        切換 viewModel.comic {
        案例加載:
            返回 AnyView(文本(“正在加載”))
        案例.result(讓漫畫):
            返回任何視圖(VStack {
                文字(漫畫.標題)
                RemoteImage(網址:comic.img)
            })
        案例.錯誤:
            返回 AnyView(文本(“錯誤”))
        }
    }
}

就是這樣,iOS 應用程序也準備好了!

使用 KMM 開發的 iOS 應用程序

概括

最後,回答標題中的問題——Kotlin Multiplatform 是跨平台開發的未來嗎? – 這一切都取決於需求。 如果您想同時為兩個移動平台創建一個小的、相同的應用程序,那麼可能不會,因為您需要具備兩個平台的開發所需的知識。

發布產品圖標

與我們的專家一起為您開發下一個應用程序

獲取報價

但是,如果您已經擁有一支由 Android 和 iOS 開發人員組成的團隊,並且想要提供最佳的用戶體驗,那麼它可以顯著減少開發時間。 就像在提供的示例中一樣,由於共享模塊,應用程序邏輯只實現了一次,並且用戶界面以完全特定於平台的方式創建。 那麼為什麼不試一試呢? 如您所見,它很容易上手。

從業務角度對跨平台開發感到好奇嗎? 查看我們關於跨平台開發優勢的文章。