[譯] 使用 Kotlin 將你的應用程式從 iOS 轉換成 Android

iWeslie發表於2018-12-02

通過本教程,你將親眼看到語言的相似之處,以及通過 iOS 應用程式移植到 Android 上了解將 Swift 轉換為 Kotlin 是多麼容易。

移動裝置是你的日常伴侶,無論你走到哪裡,都可以將它們放入揹包和口袋。技術將不斷適應你的移動裝置,使移動開發作為一個人的業餘愛好或是專業都將越來越普遍。

通常開發人員會選擇一個開發平臺,最常見的是 Android 或 iOS。這種選擇一般基於個人所能得到的資源(例如,她或他的個人裝置)或當前市場環境。許多開發人員傾向於只為他們選擇的平臺構建應用程式。Android 和 iOS 工程師歷來都使用完全不同的語言和 IDE ,儘管兩個移動平臺之間存在太多相似之處,但在兩個平臺之間的跨平臺開發是令人生畏並且十分罕見的。

但是 Android 和 iOS 開發的語言和工具在過去幾年中得到了極大的改善,主要是 KotlinSwift 的誕生,這兩種語言緩解了跨平臺學習之間的障礙。掌握了語言基礎知識後,你可以很容易地把程式碼可以從 Swift 轉換為 Kotlin。

[譯] 使用 Kotlin 將你的應用程式從 iOS 轉換成 Android

在本教程中,你將親眼看到這些語言的相似之處,以及將 Swift 轉換為 Kotlin 是多麼容易。首先開啟 Xcode,你將探索使用 Swift 來編寫一個 iOS 應用程式,然後你將在 Android Studio 中使用 Kotlin 重新編寫這個相同的應用程式。

注意:熟悉 Swift 和 iOS 的基礎知識或 Kotlin 和 Android 的基礎知識有助於更好的完成本教程。你可以通過 這些教程 瞭解適用於 iOS 的 Swift 或 這些教程瞭解 Android 上的 Kotlin。

開始

下載 測試專案 來開始本教程的學習。

iOS APP 架構

在 Xcode 中開啟 iOS-Finished 示例應用程式並在模擬器上執行它,該應用程式將提示你登入。輸入使用者名稱和任意至少六個字元長的和密碼來登入該應用程式,密碼還至少包含一個數字字元。

Screenshot of login screen

通過使用者名稱和密碼登入 APP,進入專案主頁面:熊出沒,注意!戳一戳它看看會發生什麼……

Screenshot of app with image of bear and Poke button

現在你可以關注在這個簡單應用程式架構中的兩個 ViewController,每個互動頁面都有一個。

  1. LoginViewController
  2. BearViewController

在專案中找到這些控制器,該應用程式首先載入 LoginViewController,它持有了 Main.storyboard 中定義的 TextFieldButton 的 UI 元件。另外請注意,LoginViewController 包含了兩個用於驗證密碼的輔助函式,以及兩個用於顯示無效密碼錯誤的輔助函式,這些是你將在 Kotlin 中重寫的兩個 Swift 函式。

BearViewController 中也持有了 Main.storyboard 中的 UI 元件。通常在 iOS 開發中,每個 ViewController 都有其單獨的 storyboard 頁面。在本教程中你將專注於 ViewController 邏輯元件而不是 UI。在 BearViewController 中,你保持對一個名為 tapCount的變數的引用,每次你點選 pokeButtontapCount 值都會更新,從而觸發熊的不同狀態。

Swift 到 Kotlin:基礎部分

現在你已經對該應用程式有了個大體的瞭解,現在可以通過 playground 進行技術改造,深入瞭解一些語言方面的細節。對於 Swift,在 Xcode 裡點選 File ▸ New ▸ Playground 來建立一個新的 Blank 的 playground,然後就可以候編寫一些程式碼了!

Menu File ▸ New ▸ Playground

Blank Swift playground option

變數和可選項

在 Swift 中有一個稱為 可選項 的概念。可選值包含一個值或為 nil。將此程式碼貼上到 Swift playground:

var hello = "world"
hello = "ok"

let hey = "world"
hey = "no"
複製程式碼

Swift 使用 varlet 來定義變數,兩個字首定義了可變性。let 宣告變數之後不可修改,這就是編譯器報錯的原因,而 var 變數可以在執行時更改。

Cannot assign value compiler error

在 playground 中為程式碼新增型別宣告,現在它看起來會像這樣:

var hello: String? = "world"
hello = "ok"

let hey: String = "world"
hey = "no"
複製程式碼

通過為這兩個變數增加型別標註,你已經將 hello 設定為 可為空 的String,由 String? 中的 ? 表示,而 hey 是一個 非空 的 String。可為空的變數在 Swift 中稱為 可選項

為什麼這個細節很重要?空值通常會導致應用程式中出現令人討厭的崩潰,尤其是當你的資料來源並不是始終在客戶端中進行定義時(例如,如果你希望伺服器獲得某個值而且它並沒有返回)。使用 letvar 之類的簡單字首允許你進行內建的動態檢查以防止程式在值為空時進行編譯。有關更多資訊,請參閱有關 Swift 中函式程式設計的 相關教程

但是 Android 又會是怎樣呢?可空性通常被認為是 Java 開發中最大的痛點之一。NPE(或空指標異常)通常是由於空值處理不當導致程式崩潰的原因。在 Java 中,你可以做的最有效的事是使用 @NonNull@Nullable 註解來警告該值是否可為空。但是這些警告不會阻止你編譯和執行應用程式。幸運的是,Kotlin 拯救了它!有關更多資訊,請參閱 Kotlin 的介紹

try.kotlinlang.org 開啟 Kotlin playground 並貼上剛剛在 Swift playground 中編寫的程式碼來替換 main 函式的主體:

Kotlin playground with main function body replaced

太棒了對吧?你可以將程式碼從一個 playground 複製到另一個 playground,即使這兩個 playground 使用不同的語言。當然,語法並不完全相同。Kotlin 使用 val 代替 let,所以現在將該關鍵詞更改為 Kotlin 中宣告不可變變數的方式,如下所示:

fun main(args: Array<String>) {
  var hello: String? = "world"
  hello = "ok"

  val hey: String = "world"
  hey = "no"
}
複製程式碼

既然你做出了改變,現在你將 let 轉向 val,然後你就得到了 Kotlin 程式碼!

點選右上角的 Run,你會看到一個有意義的錯誤:

Kotlin compiler error

這就是你在 Swift playground 看到的同樣的東西。就像 let 一樣你也不能重新給 val 賦值。

運算元組(Map)

陣列在 Swift 裡作為一等公民,操作起來功能非常強大。如果要將整數陣列中的所有值加倍,只需呼叫 map 函式並將其中每個值乘以 2 即可。將此程式碼貼上到 Swift playground 中。

let xs = [1, 2, 3]
print(xs.map { $0 * 2 })
複製程式碼

在 Kotlin裡,你也可以這樣做!再一次的,將程式碼從 Swift playground 複製並貼上到 Kotlin playground。再進行修改以使其與 Kotlin 語法後,你將得到以下內容:

val xs = listOf(1, 2, 3)
print(xs.map { it * 2 })
複製程式碼

為了到這一步,你需要:

  1. 像上一個示例中那樣,再次把 let 改為 val
  2. 使用 listOf() 而不是方括號來更改宣告整數陣列的方式。
  3. 在 map 函式裡把 $0 改為 it 以引用其中的值。$0 表示 Swift 中閉包的第一個元素,而在 Kotlin 中你要在 lambda 表示式中使用保留關鍵字。

這比手動一次次遍歷陣列的每一個值再將所有整數乘以 2 要好得多!

獎勵:現在看看你可以在 Swift 和 Kotlin 中對陣列應用哪些其函式把!使整數翻倍就是一個很好的例子。或者可以使用 filter 來過濾陣列中的特定值,或者 flatMap(另一個非常強大的內建陣列運算子),用於展平巢狀陣列。這是 Kotlin 的一個例子,你可以執行 Kotlin playground 了:

val xs = listOf(listOf(1, 2, 3))
print(xs.flatMap { it.map { it * 2 }})
複製程式碼

你可以繼續使用 Swift 和 Kotlin 的所有優點,但是你沒時間用 Kotlin 編寫你的 Poke the Bear 應用程式了,你肯定不希望像平常一樣丟給你的 Android 使用者一個 iOS 應用程式而讓他們不知所措!

編寫 Android 應用程式

在 Android Studio 3.1.4 或更高版本中開啟 Android Starter 應用。你可以點選 File ▸ New ▸ Import Project 來匯入專案,然後選擇 Kotlin-Starter 專案的根資料夾來開啟專案。這個專案比之前完成的 iOS 應用程式更加簡單,但不要害怕!本教程將指導你構建 Android 版本的應用程式!

實現 LoginActivity

開啟 app ▸ java ▸ com.raywenderlich.pokethebear ▸ LoginActivity.kt 檔案。這和了 iOS 專案中的 LoginViewController 類似。Android 入門專案有一個與此 activity 相對應的 XML 佈局檔案,請開啟 app ▸ res ▸ layout ▸ activity_login.xml 以引用你將在此處使用的檢視,即 login_buttonpassword_edit_text

Android login screen

輸入驗證

現在你將從 Swift 專案檔案 LoginViewController.swift 複製的第一個名為 containsNumbers 的函式:

private func containsNumbers(string: String) -> Bool {
  let numbersInString = string.filter { ("0"..."9").contains($0) }
  return !numbersInString.isEmpty
}
複製程式碼

再一次地,你可以使用你激進的跨平臺複製貼上方法,複製該函式並將其貼上到 Android Studio 中 LoginActivity.kt 檔案中的 LoginActivity 類中。做了一些更改之後,以下是你現在的 Kotlin 程式碼:

private fun containsNumbers(string: String): Boolean {
  val numbersInString = string.filter { ("0".."9").contains(it.toString()) }
  return numbersInString.isNotEmpty()
}
複製程式碼
  1. 正如你之前在 playground 上所做的那樣,將 let 更改為 val 以宣告不可變的返回值。
  2. 對於函式宣告,你可以體會到從 func 中刪除 'c' 獲得的一些樂趣!你使用 fun 而不是 func 來宣告 Kotlin 裡的函式。
  3. Kotlin 中函式的返回值用冒號 : 表示而不是 lambda 符號 ->
  4. 此外,在 Kotlin 中,布林值被稱為 Boolean 而不是 Bool
  5. 要在 Kotlin 中有宣告一個閉區間 Range,你需要使用兩個點而不是三個,所以 "0"..."9" 要改為 "0".."9"
  6. 就像你在 playground 中使用 map 一樣,你還必須將 $0 轉換為 it。此外,在 Kotlin 中呼叫 contain 來比較需要將 it 轉換為 String。
  7. 最後,你使用 return 語句在 Kotlin 中進行一些清理。你只需使用 Kotlin 裡 String 的函式 isNotEmpty 來檢查是否為空,而不是用 !

現在,程式碼語句從 Swift 更改為了 Kotlin。

從 iOS 專案中的 LoginViewController 複製 passwordIsValid 函式並將其貼上到 Android 專案的類中:

private func passwordIsValid(passwordInput: String) -> Bool {
  return passwordInput.count >= 6 && self.containsNumbers(string: passwordInput)
}
複製程式碼

這還需要進行適當的更改來將程式碼從 Swift 轉換為 Kotlin。你應該得到這樣的程式碼:

private fun passwordIsValid(passwordInput: String): Boolean {
  return passwordInput.length >= 6 && this.containsNumbers(string = passwordInput)
}
複製程式碼

其中還有一些細節的差異:

  1. length 而不是 count
  2. this 而不是 self
  3. string = 而不是 string:

請注意,在Kotlin 中不需要 string = 方法,它有助於保持本教程中兩種語言之間的相似性。在其他實踐裡的標籤是 Kotlin 為了使 Java 程式碼可以訪問預設函式引數而包含的更多細節。閱讀有關 @JvmOverloads 函式的 更多資訊 以瞭解有關預設引數的更多資訊!

錯誤展示

showLengthErrorshowInvalidError 函式從 iOS 專案中的 LoginViewController 複製到 Android 專案中 LoginActivity 類裡。

showLengthError 函式確定使用者輸入的密碼是否包含六個或更多字元,如果不是,則顯示相應的警報訊息:

private func showLengthError() {
  let alert = UIAlertController(title: "Error", 
    message: "Password must contain 6 or more characters", 
    preferredStyle: UIAlertControllerStyle.alert)
  alert.addAction(UIAlertAction(title: "Okay", 
    style: UIAlertActionStyle.default, 
    handler: nil))
  self.present(alert, animated: true, completion: nil)
}
複製程式碼

showInvalidError 函式用來判斷使用者輸入的密碼是否包含至少一個數字字元,如果不是,則彈出相應的警告訊息:

private func showInvalidError() {
  let alert = UIAlertController(title: "Error", 
    message: "Password must contain a number (0-9)", 
    preferredStyle: UIAlertControllerStyle.alert)
  alert.addAction(UIAlertAction(title: "Okay", 
    style: UIAlertActionStyle.default, handler: nil))
  self.present(alert, animated: true, completion: nil)
}
複製程式碼

現在,你必須在 Android 應用中將新複製的函式的程式碼並從 Swift 轉換為 Kotlin。你的新 showError 函式需要重新引入 Android 的 API。你現在將使用 AlertDialog.Builder 來實現 UIAlertController 相似的功能。你可以在 本教程 中檢視有關常見設計模式的更多資訊,比如 AlertDialog。對話方塊的標題,訊息和確定按鈕字串已包含在 strings.xml 中,因此請繼續使用它們!用以下程式碼替換 showLengthError

private fun showLengthError() {
  AlertDialog.Builder(this)
    .setTitle(getString(R.string.error))
    .setMessage(getString(R.string.length_error_body))
    .setPositiveButton(getString(R.string.okay), null)
    .show()
}
複製程式碼

使用相同的格式建立展示 showInvalidError 的 AlertDialog。用以下內容替換複製的方法:

private fun showInvalidError() {
  AlertDialog.Builder(this)
    .setTitle(getString(R.string.error))
    .setMessage(getString(R.string.invalid_error_body))
    .setPositiveButton(getString(R.string.okay), null)
    .show()
}
複製程式碼

處理按鈕點選事件

現在你已經完成了驗證和錯誤顯示功能,通過實現 loginButtonClicked 函式可以把它們放在一起。Android 和 iOS 之間需要注意的一個很有趣的區別是,你的 Android 檢視是在第一個生命週期回撥 onCreate() 中顯式建立和設定的,而 iOS 應用中的 Main.storyboard 是在 Swift 中隱式連結的。你可以在 此處 詳細瞭解本教程中的 Android 生命週期。

這是 iOS 專案中的 loginButtonTapped 函式。

@IBAction func loginButtonTapped(_ sender: Any) {
  let passwordInput = passwordEditText.text ?? ""
  if passwordIsValid(passwordInput: passwordInput) {
    self.performSegue(withIdentifier: "pushBearViewController", sender: self)
  } else if passwordInput.count < 6 {
    self.showLengthError()
  } else if !containsNumbers(string: passwordInput) {
    self.showInvalidError()
  }
}
複製程式碼

將 iOS 專案中 loginButtonTapped 函式的主體部分複製並貼上到 Android 專案中 loginButtonClicked 函式的主體中,並根據你掌握的方法對程式碼進行一些小改動,將語法從 Swift 更改為 Kotlin。

val passwordInput = this.password_edit_text.text.toString()
if (passwordIsValid(passwordInput = passwordInput)) {
  startActivity(Intent(this, BearActivity::class.java))
} else if (passwordInput.length < 6) {
  this.showLengthError()
} else if (!containsNumbers(string = passwordInput)) {
  this.showInvalidError()
}
複製程式碼

這裡有兩個不同之處,分別是從 EditText 中提取字串的方法以及顯示新 activity 的方法。你可以使用語句 this.password_edit_text.text.toString()passwordInput 檢視中獲取文字。然後,呼叫 startActivity 函式傳入 Intent 以啟動 BearActivity 活動。剩下的都應該非常簡單。

你的 LoginActivity 現已完成。現在 Android Studio 中編譯並執行應用程式,檢視你的裝置或自帶模擬器中顯示的第一個已實現的活動。輸入使用者名稱的任何字串值,並使用有效和無效的密碼組合,以確保你的錯誤對話方塊顯示達到了預期。

Kotlin login screen

成功登入後將螢幕上將顯示一隻熊,你現在可以來實現它了!

實現熊的活動

開啟 app ▸ java ▸ com.raywenderlich.pokethebear ▸ BearActivity.kt,你的 BearViewController.swift 檔案即將變成 Kotlin 的版本。你將通過實現輔助函式 bearAttackreset 來開始修改此 Activity。你將在 Swift 檔案中看到 bearAttack 負責設定 UI 狀態,隱藏 Poke 按鈕五秒鐘,然後重置螢幕:

private func bearAttack() {
  self.bearImageView.image = imageLiteral(resourceName: "bear5")
  self.view.backgroundColor = .red
  self.pokeButton.isHidden = true
  DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(5), 
    execute: { self.reset() })
}
複製程式碼

從 iOS 專案中複製該函式並將其貼上到 Android 專案中的 bearAttack 函式體中,然後進行一些小的語法修改讓 Kotlin 中的 bearAttack 函式的主體如下所示:

private fun bearAttack() {
  this.bear_image_view.setImageDrawable(getDrawable(R.drawable.bear5))
  this.bear_container.setBackgroundColor(getColor(R.color.red))
  this.poke_button.visibility = View.INVISIBLE
  this.bear_container.postDelayed({ reset() }, TimeUnit.SECONDS.toMillis(5))
}
複製程式碼

你需要做出以下修改:

  1. 呼叫 setImageDrawable 函式將 bear_image_view 的影象資源設定為 bear5.png 可繪製的資源,該資源已包含在 app ▸ res ▸ drawable 目錄下。
  2. 然後呼叫 setBackgroundColor 函式將 bear_container 檢視的背景設定為預先定義的顏色 R.color.red
  3. isHidden 屬性更改為 visibility,而不是將按鈕的可見性切換為 View.INVISIBLE
  4. 也許你對程式碼的最不直觀的改變是重寫 DispatchQueue,但不要害怕!Android的 asyncAfter 是一個簡單的 postDelayed 動作,你在 bear_container 檢視上設定。

幾乎就要完成了!還有另一個要從 Swift 轉換為 Kotlin 的功能。複製 Swift reset 函式的主體並將其貼上到Android專案的 BearActivity 類重置中來重複這個轉換過程:

self.tapCount = 0
self.bearImageView.image = imageLiteral(resourceName: "bear1")
self.view.backgroundColor = .white
self.pokeButton.isHidden = false
複製程式碼

然後進行類似的更改:

this.tapCount = 0
this.bear_image_view.setImageDrawable(getDrawable(R.drawable.bear1))
this.bear_container.setBackgroundColor(getColor(R.color.white))
this.poke_button.visibility = View.VISIBLE
複製程式碼

最後一步是從 iOS 專案中複製 pokeButtonTapped 函式的主體並將其貼上到 Android 專案中。由於 Swift 和 Kotlin 之間的相似性,這個 if/else 語句將也需要對 Android 作出更改,雖然修改非常小。這樣確保了你的 Kotlin 中 pokeButtonClicked 函式主體看起來像這樣:

this.tapCount = this.tapCount + 1
if (this.tapCount == 3) {
  this.bear_image_view.setImageDrawable(getDrawable(R.drawable.bear2))
} else if (this.tapCount == 7) {
  this.bear_image_view.setImageDrawable(getDrawable(R.drawable.bear3))
} else if (this.tapCount == 12) {
  this.bear_image_view.setImageDrawable(getDrawable(R.drawable.bear4))
} else if (this.tapCount == 20) {
  this.bearAttack()
}
複製程式碼

額外宣告:這個if/else 階梯語句可以很容易地用更具表現力的 控制流語句 替換,比如 switch,也就是在 Kotlin 中的 when

如果你想簡化邏輯,請嘗試一下。

現在所有功能都已從 iOS 應用程式移植並從 Swift 轉換為 Kotlin。在真機或模擬器中編譯並執行應用程式並開始使用,現在你可以在登入螢幕後面出現了你的毛茸茸的朋友。

Android screen with bear and poke button

恭喜你,你已將 Swift 轉換為 Kotlin,將 iOS 應用程式轉換為全新的 Android 應用程式。你已經通過將 Swift 程式碼從 Xcode 中的 iOS 專案移動到 Android Studio 中的 Android 應用程式,將 Swift 轉換為 Kotlin 來實現跨平臺!沒有多少人和開發人員會說什麼,而且實現它真的非常簡單。

接下來該幹嘛?

使用本教程頂部的 連結 下載已經完成的專案來看看它是如何進行的。

如果你是一名 Swift 開發人員,或者是 Kotlin 新手,請檢視 Kotlin 官方文件 以更深入地瞭解這些語言。你已經知道如何執行 Kotlin playground 來嘗試用程式碼片段,並且可以在文件中編寫可執行的程式碼小部件。如果你已經是 Kotlin 開發人員,請嘗試在 Swift 中編寫應用程式。

如果你喜歡 Swift 和 Kotlin 的並排比較,請在 本文 中檢視更多內容。你可信賴的作者還與 UIConf 的 iOS 同事就 Swift 和 Kotlin 進行了一次快速的討論,你可以在 這裡 觀看到。

我們希望你喜歡本教程,瞭解如何把 Swift 編寫的 iOS 應用程式變成用 Kotlin 建立一個全新的 Android 應用程式。我們也希望你繼續探索這兩種語言和兩種平臺。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章