บทนำสู่ Kotlin: การเขียนโปรแกรม Android สำหรับมนุษย์

เผยแพร่แล้ว: 2022-03-11

ในโลก Android ที่สมบูรณ์แบบ ภาษาหลักของ Java นั้นทันสมัย ​​ชัดเจน และสง่างามจริงๆ คุณสามารถเขียนน้อยลงโดยทำมากขึ้น และเมื่อใดก็ตามที่คุณลักษณะใหม่ปรากฏขึ้น นักพัฒนาสามารถใช้งานได้โดยการเพิ่มเวอร์ชันใน Gradle จากนั้นในขณะที่สร้างแอปที่ดีมาก ดูเหมือนว่าจะสามารถทดสอบ ขยาย และบำรุงรักษาได้อย่างเต็มที่ กิจกรรมของเราไม่ใหญ่และซับซ้อนเกินไป เราสามารถเปลี่ยนแหล่งข้อมูลจากฐานข้อมูลเป็นเว็บได้โดยไม่มีความแตกต่างมากมาย เป็นต้น ฟังดูดีใช่มั้ย? น่าเสียดายที่โลกของ Android ไม่เหมาะอย่างยิ่ง Google ยังคงมุ่งมั่นเพื่อความสมบูรณ์แบบ แต่เราทุกคนรู้ว่าโลกในอุดมคติไม่มีอยู่จริง ดังนั้น เราต้องช่วยตัวเองในการเดินทางที่ยิ่งใหญ่ในโลกของ Android

Kotlin คืออะไรและทำไมคุณจึงควรใช้

ดังนั้นภาษาแรก ฉันคิดว่า Java ไม่ใช่ผู้เชี่ยวชาญด้านความสง่างามหรือความชัดเจน และไม่ทันสมัยและไม่แสดงออก (และฉันเดาว่าคุณจะเห็นด้วย) ข้อเสียคือ ต่ำกว่า Android N เรายังจำกัดอยู่ที่ Java 6 (รวมถึงส่วนเล็กๆ ของ Java 7) นักพัฒนายังสามารถแนบ RetroLambda เพื่อใช้นิพจน์แลมบ์ดาในโค้ดของพวกเขา ซึ่งมีประโยชน์มากในขณะที่ใช้ RxJava เหนือ Android N เราสามารถใช้ฟังก์ชันใหม่ของ Java 8 ได้ แต่ก็ยังเป็น Java ที่เก่าและหนักอยู่ บ่อยครั้งที่ฉันได้ยินนักพัฒนา Android พูดว่า "ฉันหวังว่า Android จะสนับสนุนภาษาที่ดีกว่าเช่น iOS กับ Swift" แล้วถ้าฉันบอกคุณว่าคุณสามารถใช้ภาษาที่สวยงามและเรียบง่าย พร้อมด้วย null safety, lambdas และคุณสมบัติใหม่ที่ดีอื่นๆ อีกมากมายได้ ยินดีต้อนรับสู่ Kotlin

Kotlin คืออะไร?

Kotlin เป็นภาษาใหม่ (บางครั้งเรียกว่า Swift สำหรับ Android) พัฒนาโดยทีม JetBrains และขณะนี้อยู่ในเวอร์ชัน 1.0.2 สิ่งที่ทำให้มีประโยชน์ในการพัฒนา Android คือคอมไพล์เป็น JVM bytecode และสามารถคอมไพล์เป็น JavaScript ได้เช่นกัน มันเข้ากันได้อย่างสมบูรณ์กับ Java และโค้ด Kotlin สามารถแปลงเป็นโค้ด Java ได้ง่ายๆ และในทางกลับกัน (มีปลั๊กอินจาก JetBrains) นั่นหมายความว่า Kotlin สามารถใช้เฟรมเวิร์ก ไลบรารี และอื่นๆ ที่เขียนด้วยภาษา Java ได้ บน Android จะผสานรวมโดย Gradle หากคุณมีแอพ Android อยู่แล้วและต้องการใช้คุณสมบัติใหม่ใน Kotlin โดยไม่ต้องเขียนใหม่ทั้งแอพ เพียงแค่เริ่มเขียนใน Kotlin มันก็จะใช้งานได้

แต่ 'คุณสมบัติใหม่ที่ยอดเยี่ยม' คืออะไร? ให้ฉันแสดงรายการบางส่วน:

พารามิเตอร์ฟังก์ชั่นเสริมและตั้งชื่อ

 fun createDate(day: Int, month: Int, year: Int, hour: Int = 0, minute: Int = 0, second: Int = 0) { print("TEST", "$day-$month-$year $hour:$minute:$second") }

เราสามารถเรียกเมธอด createDate ด้วยวิธีต่างๆ ได้

 createDate(1,2,2016) prints: '1-2-2016 0:0:0' createDate(1,2,2016, 12) prints: '1-2-2016 12:0:0' createDate(1,2,2016, minute = 30) prints: '1-2-2016 0:30:0'

ความปลอดภัยเป็นศูนย์

หากตัวแปรสามารถเป็นค่าว่างได้ โค้ดจะไม่คอมไพล์เว้นแต่เราจะบังคับให้สร้าง รหัสต่อไปนี้จะมีข้อผิดพลาด - nullableVar อาจเป็นโมฆะ:

 var nullableVar: String? = “”; nullableVar.length;

ในการรวบรวมเราต้องตรวจสอบว่าไม่เป็นโมฆะหรือไม่:

 if(nullableVar){ nullableVar.length }

หรือสั้นกว่า:

 nullableVar?.length

ด้วยวิธีนี้ หาก nullableVar เป็นโมฆะ จะไม่มีอะไรเกิดขึ้น มิฉะนั้น เราสามารถทำเครื่องหมายตัวแปรว่าไม่สามารถเป็นค่าว่างได้ โดยไม่ต้องใส่เครื่องหมายคำถามหลังจากพิมพ์:

 var nonNullableVar: String = “”; nonNullableVar.length;

รหัสนี้คอมไพล์ และถ้าเราต้องการกำหนด null ให้กับ nonNullableVar คอมไพเลอร์จะแสดงข้อผิดพลาด

นอกจากนี้ยังมีตัวดำเนินการ Elvis ที่มีประโยชน์มาก:

 var stringLength = nullableVar?.length ?: 0

จากนั้น เมื่อ nullableVar เป็น null (ดังนั้น nullableVar?.length จะคืนค่า null) stringLength จะมีค่าเป็น 0

ตัวแปรที่ไม่แน่นอนและไม่เปลี่ยนรูป

ในตัวอย่างข้างต้น ฉันใช้ var เมื่อกำหนดตัวแปร สิ่งนี้เปลี่ยนแปลงได้ เราสามารถกำหนดใหม่ได้ทุกเมื่อที่ต้องการ หากเราต้องการให้ตัวแปรนั้นไม่เปลี่ยนรูป (ในหลาย ๆ กรณีเราควร) เราใช้ val (เป็นค่า ไม่ใช่ตัวแปร):

 val immutable: Int = 1

หลังจากนั้นคอมไพเลอร์จะไม่อนุญาตให้เรากำหนดใหม่ให้เป็นแบบไม่เปลี่ยนรูป

แลมบ์ดาส

เราทุกคนรู้ดีว่าแลมบ์ดาคืออะไร ดังนั้นที่นี่ฉันจะแสดงให้เห็นว่าเราใช้มันใน Kotlin ได้อย่างไร:

 button.setOnClickListener({ view -> Log.d("Kotlin","Click")})

หรือถ้าฟังก์ชันเป็นอาร์กิวเมนต์เดียวหรืออาร์กิวเมนต์สุดท้าย:

 button.setOnClickListener { Log.d("Kotlin","Click")}

ส่วนขยาย

ส่วนขยายเป็นคุณลักษณะด้านภาษาที่มีประโยชน์มาก ซึ่งต้องขอบคุณการที่เรา "ขยาย" ชั้นเรียนที่มีอยู่ ถึงแม้ว่าส่วนขยายนั้นจะสิ้นสุดแล้วหรือเราไม่สามารถเข้าถึงซอร์สโค้ดได้

ตัวอย่างเช่น หากต้องการรับค่าสตริงจากแก้ไขข้อความ แทนที่จะเขียนทุกครั้งที่ editText.text.toString() เราสามารถเขียนฟังก์ชันได้:

 fun EditText.textValue(): String{ return text.toString() }

หรือสั้นกว่า:

 fun EditText.textValue() = text.toString()

และตอนนี้ ทุกอินสแตนซ์ของ EditText:

 editText.textValue()

หรือเราสามารถเพิ่มคุณสมบัติที่ส่งคืนได้:

 var EditText.textValue: String get() = text.toString() set(v) {setText(v)}

ผู้ประกอบการโอเวอร์โหลด

บางครั้งมีประโยชน์ถ้าเราต้องการเพิ่ม คูณ หรือเปรียบเทียบวัตถุ Kotlin อนุญาตให้โอเวอร์โหลดตัวดำเนินการไบนารี (บวก ลบ plusAssign ช่วง ฯลฯ ) ตัวดำเนินการอาร์เรย์ (รับ ตั้งค่า รับช่วง ตั้งค่าช่วง) และการดำเนินการเท่ากับและเอก (+a, -a ฯลฯ)

ชั้นข้อมูล

คุณต้องใช้โค้ดกี่บรรทัดเพื่อใช้งานคลาส User ใน Java โดยมีคุณสมบัติสามประการ: copy, equals, hashCode และ toString ใน Kaotlin คุณต้องการเพียงหนึ่งบรรทัด:

 data class User(val name: String, val surname: String, val age: Int)

คลาสข้อมูลนี้มีเมธอด equals(), hashCode() และ copy() และ toString() ซึ่งพิมพ์ User เป็น:

 User(name=John, surname=Doe, age=23)

คลาสข้อมูลยังมีฟังก์ชันและคุณสมบัติที่มีประโยชน์อื่นๆ ซึ่งคุณสามารถดูได้ในเอกสารประกอบของ Kotlin

ส่วนขยาย Anko

คุณใช้ Butterknife หรือส่วนขยาย Android ใช่ไหม จะเป็นอย่างไรถ้าคุณไม่จำเป็นต้องใช้ไลบรารีนี้ และหลังจากประกาศมุมมองใน XML แล้ว ให้ใช้จากโค้ดตาม ID ของมัน (เช่นเดียวกับ XAML ใน C#):

 <Button android: android:layout_width="match_parent" android:layout_height="wrap_content" />
 loginBtn.setOnClickListener{}

Kotlin มีส่วนขยายของ Anko ที่มีประโยชน์มาก และด้วยสิ่งนี้ คุณไม่จำเป็นต้องบอกกิจกรรมของคุณว่า loginBtn คืออะไร มันรู้เพียงแค่ "นำเข้า" xml:

 import kotlinx.android.synthetic.main.activity_main.*

มีประโยชน์อื่นๆ อีกมากมายใน Anko รวมถึงการเริ่มกิจกรรม การแสดงขนมปังปิ้ง และอื่นๆ นี่ไม่ใช่เป้าหมายหลักของ Anko แต่ได้รับการออกแบบมาเพื่อสร้างเลย์เอาต์จากโค้ดได้อย่างง่ายดาย ดังนั้น หากคุณต้องการสร้างเลย์เอาต์โดยทางโปรแกรม นี่คือวิธีที่ดีที่สุด

นี่เป็นเพียงมุมมองสั้นๆ ของ Kotlin ฉันแนะนำให้อ่านบล็อกของ Antonio Leiva และหนังสือของเขา - Kotlin สำหรับนักพัฒนา Android และแน่นอนว่าเป็นเว็บไซต์ Kotlin อย่างเป็นทางการ

MVP คืออะไรและทำไม?

ภาษาที่ดี ทรงพลัง และชัดเจนไม่เพียงพอ มันง่ายมากที่จะเขียนแอพยุ่ง ๆ กับทุกภาษาโดยไม่มีสถาปัตยกรรมที่ดี นักพัฒนา Android (ส่วนใหญ่เป็นผู้ที่กำลังเริ่มต้น แต่ยังเป็นขั้นสูง) มักจะให้ความรับผิดชอบกิจกรรมสำหรับทุกสิ่งรอบตัว กิจกรรม (หรือส่วนย่อย หรือมุมมองอื่นๆ) ดาวน์โหลดข้อมูล ส่งเพื่อบันทึก นำเสนอ ตอบสนองต่อการโต้ตอบของผู้ใช้ แก้ไขข้อมูล จัดการมุมมองย่อยทั้งหมด . . และบ่อยครั้งมากขึ้น มันมากเกินไปสำหรับวัตถุที่ไม่เสถียรเช่นกิจกรรมหรือชิ้นส่วน (เพียงแค่หมุนหน้าจอและกิจกรรมบอกว่า 'ลาก่อน….')

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

MVC กับ MVP

“เฮ้ ฉันรู้อะไรแบบนั้น เรียกว่า MVC!” - คุณไม่ได้คิด? ไม่ MVP ไม่เหมือนกับ MVC ในรูปแบบ MVC มุมมองของคุณสื่อสารกับโมเดลได้ ขณะใช้ MVP คุณไม่อนุญาตให้มีการสื่อสารใดๆ ระหว่างเลเยอร์ทั้งสองนี้ วิธีเดียวที่ View สามารถสื่อสารกับโมเดลได้คือผ่าน Presenter สิ่งเดียวที่ View รู้เกี่ยวกับ Model ก็คือโครงสร้างข้อมูล ดูรู้วิธี เช่น แสดง User แต่ไม่รู้ว่าเมื่อไร นี่เป็นตัวอย่างง่ายๆ:

View รู้ดีว่า “ฉันเป็นกิจกรรม ฉันมีสอง EditTexts และหนึ่งปุ่ม เมื่อมีคนคลิกปุ่ม ฉันควรบอกผู้นำเสนอ และส่งค่าของ EditTexts ให้เขา แค่นี้ฉันก็หลับได้จนกว่าคนคลิกถัดไปหรือผู้นำเสนอจะบอกฉันว่าต้องทำอย่างไร”

ผู้นำเสนอรู้ว่าที่ใดที่หนึ่งคือมุมมอง และเขารู้ว่ามุมมองนี้สามารถดำเนินการใดได้บ้าง เขารู้ด้วยว่าเมื่อได้รับสองสตริง เขาควรสร้างผู้ใช้จากสองสตริงนี้และส่งข้อมูลไปยังโมเดลเพื่อบันทึก และหากการบันทึกสำเร็จ ให้บอกมุมมอง 'แสดงข้อมูลความสำเร็จ'

โมเดลเพียงแค่รู้ว่าข้อมูลอยู่ที่ไหน ควรบันทึกที่ไหน และควรดำเนินการใดกับข้อมูล

แอปพลิเคชันที่เขียนด้วย MVP นั้นง่ายต่อการทดสอบ บำรุงรักษา และนำกลับมาใช้ใหม่ ผู้นำเสนอที่บริสุทธิ์ไม่ควรรู้อะไรเกี่ยวกับแพลตฟอร์ม Android ควรเป็นคลาส Java แท้ (หรือ Kotlin ในกรณีของเรา) ด้วยเหตุนี้ เราจึงนำพรีเซ็นเตอร์ของเรากลับมาใช้ในโครงการอื่นๆ ได้ นอกจากนี้เรายังสามารถเขียนการทดสอบหน่วย การทดสอบแยกจากกัน Model, View และ Presenter ได้อย่างง่ายดาย

รูปแบบ MVP นำไปสู่ ​​codebase ที่ดีขึ้นและซับซ้อนน้อยลงโดยแยกส่วนติดต่อผู้ใช้และตรรกะทางธุรกิจออกจากกันอย่างแท้จริง

พูดนอกเรื่องเล็กน้อย: MVP ควรเป็นส่วนหนึ่งของ Clean Architecture ของ Uncle Bob เพื่อให้แอปพลิเคชันมีความยืดหยุ่นและมีสถาปัตยกรรมที่สวยงามมากยิ่งขึ้น ฉันจะพยายามเขียนเกี่ยวกับเรื่องนั้นในครั้งต่อไป

แอพตัวอย่างพร้อม MVP และ Kotlin

ทฤษฎีเพียงพอแล้ว มาดูโค้ดกัน! โอเค เรามาลองสร้างแอปง่ายๆ กัน เป้าหมายหลักสำหรับแอพนี้คือการสร้างผู้ใช้ หน้าจอแรกจะมี EditTexts สองอัน (ชื่อและนามสกุล) และหนึ่งปุ่ม (บันทึก) หลังจากป้อนชื่อและนามสกุลแล้วคลิก "บันทึก" แอปควรแสดง "ผู้ใช้ได้รับการบันทึกแล้ว" และไปที่หน้าจอถัดไปซึ่งจะแสดงชื่อและนามสกุลที่บันทึกไว้ เมื่อชื่อหรือนามสกุลว่างเปล่า แอปไม่ควรบันทึกผู้ใช้และแสดงข้อผิดพลาดที่ระบุว่ามีอะไรผิดปกติ

สิ่งแรกหลังจากสร้างโปรเจ็กต์ Android Studio คือการกำหนดค่า Kotlin คุณควรติดตั้งปลั๊กอิน Kotlin และหลังจากรีสตาร์ทแล้ว ในเครื่องมือ > Kotlin คุณสามารถคลิก 'กำหนดค่า Kotlin ในโครงการ' IDE จะเพิ่มการพึ่งพา Kotlin ให้กับ Gradle หากคุณมีโค้ดอยู่แล้ว คุณสามารถแปลงเป็น Kotlin ได้ง่ายๆ โดย (Ctrl+Shift+Alt+K หรือ Code > Convert Java file to Kotlin) หากมีบางอย่างผิดปกติและโปรเจ็กต์ไม่คอมไพล์ หรือ Gradle ไม่เห็น Kotlin คุณสามารถตรวจสอบโค้ดของแอปที่มีใน GitHub ได้

Kotlin ไม่เพียงแต่ทำงานร่วมกันได้ดีกับเฟรมเวิร์กและไลบรารีของ Java เท่านั้น แต่ยังช่วยให้คุณใช้เครื่องมือเดิมส่วนใหญ่ที่คุณคุ้นเคยอยู่แล้วต่อไปได้

ตอนนี้เรามีโปรเจ็กต์แล้ว มาเริ่มด้วยการสร้างมุมมองแรกของเรา - CreateUserView มุมมองนี้ควรมีฟังก์ชันการทำงานที่กล่าวถึงก่อนหน้านี้ เพื่อให้เราสามารถเขียนอินเทอร์เฟซสำหรับสิ่งนั้น:

 interface CreateUserView : View { fun showEmptyNameError() /* show error when name is empty */ fun showEmptySurnameError() /* show error when surname is empty */ fun showUserSaved() /* show user saved info */ fun showUserDetails(user: User) /* show user details */ }

อย่างที่คุณเห็น Kotlin นั้นคล้ายกับ Java ในการประกาศฟังก์ชัน ทั้งหมดนี้เป็นฟังก์ชันที่ไม่ส่งคืนสิ่งใดเลย และตัวสุดท้ายมีพารามิเตอร์เดียว นี่คือความแตกต่างประเภทพารามิเตอร์มาหลังชื่อ อินเทอร์เฟซ View ไม่ได้มาจาก Android แต่เป็นอินเทอร์เฟซที่เรียบง่ายและว่างเปล่าของเรา:

 interface View

อินเทอร์เฟซของ Basic Presenter ควรมีคุณสมบัติประเภท View และอย่างน้อยในเมธอด (เช่น onDestroy) โดยคุณสมบัตินี้จะถูกตั้งค่าเป็น null:

 interface Presenter<T : View> { var view: T? fun onDestroy(){ view = null } }

คุณสามารถดูคุณสมบัติ Kotlin อื่นได้ที่นี่ - คุณสามารถประกาศคุณสมบัติในอินเทอร์เฟซ และใช้วิธีการที่นั่นได้

CreateUserView ของเราต้องสื่อสารกับ CreateUserPresenter ฟังก์ชันเพิ่มเติมเพียงอย่างเดียวที่ Presenter ต้องการคือ saveUser ที่มีอาร์กิวเมนต์สตริงสองอาร์กิวเมนต์:

 interface CreateUserPresenter<T : View>: Presenter<T> { fun saveUser(name: String, surname: String) }

นอกจากนี้เรายังต้องการคำจำกัดความของโมเดล - มีการกล่าวถึงคลาสข้อมูลก่อนหน้านี้:

 data class User(val name: String, val surname: String)

หลังจากประกาศอินเทอร์เฟซทั้งหมดแล้ว เราสามารถเริ่มใช้งานได้

CreateUserPresenter จะถูกนำไปใช้ใน CreateUserPresenterImpl:

 class CreateUserPresenterImpl(override var view: CreateUserView?): CreateUserPresenter<CreateUserView> { override fun saveUser(name: String, surname: String) { } }

บรรทัดแรกพร้อมคำจำกัดความของคลาส:

 CreateUserPresenterImpl(override var view: CreateUserView?)

คอนสตรัคเตอร์ เราใช้เพื่อกำหนดคุณสมบัติมุมมองที่กำหนดไว้ในอินเทอร์เฟซ

MainActivity ซึ่งเป็นการใช้งาน CreateUserView ของเราจำเป็นต้องมีการอ้างอิงถึง CreateUserPresenter:

 class MainActivity : AppCompatActivity(), CreateUserView { private val presenter: CreateUserPresenter<CreateUserView> by lazy { CreateUserPresenterImpl(this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) saveUserBtn.setOnClickListener{ presenter.saveUser(userName.textValue(), userSurname.textValue()) /*use of textValue() extension, mentioned earlier */ } } override fun showEmptyNameError() { userName.error = getString(R.string.name_empty_error) /* it's equal to userName.setError() - Kotlin allows us to use property */ } override fun showEmptySurnameError() { userSurname.error = getString(R.string.surname_empty_error) } override fun showUserSaved() { toast(R.string.user_saved) /* anko extension - equal to Toast.makeText(this, R.string.user_saved, Toast.LENGTH_LONG) */ } override fun showUserDetails(user: User) { } override fun onDestroy() { presenter.onDestroy() } }

ในตอนต้นของชั้นเรียน เรากำหนดพรีเซนเตอร์ของเรา:

 private val presenter: CreateUserPresenter<CreateUserView> by lazy { CreateUserPresenterImpl(this) }

มันถูกกำหนดให้เป็นไม่เปลี่ยนรูป (val) และถูกสร้างขึ้นโดยผู้ได้รับมอบหมายขี้เกียจ ซึ่งจะถูกกำหนดในครั้งแรกที่จำเป็น ยิ่งไปกว่านั้น เรามั่นใจว่ามันจะไม่เป็นโมฆะ (ไม่มีเครื่องหมายคำถามหลังคำจำกัดความ)

เมื่อผู้ใช้คลิกที่ปุ่มบันทึก มุมมองจะส่งข้อมูลไปยังผู้นำเสนอด้วยค่า EditTexts เมื่อสิ่งนี้เกิดขึ้น ผู้ใช้ควรได้รับการบันทึก ดังนั้นเราจึงต้องใช้เมธอด saveUser ใน Presenter (และฟังก์ชันบางส่วนของโมเดล):

 override fun saveUser(name: String, surname: String) { val user = User(name, surname) when(UserValidator.validateUser(user)){ UserError.EMPTY_NAME -> view?.showEmptyNameError() UserError.EMPTY_SURNAME -> view?.showEmptySurnameError() UserError.NO_ERROR -> { UserStore.saveUser(user) view?.showUserSaved() view?.showUserDetails(user) } } }

เมื่อสร้างผู้ใช้แล้ว จะถูกส่งไปยัง UserValidator เพื่อตรวจสอบความถูกต้อง จากนั้นตามผลการตรวจสอบจะเรียกวิธีการที่เหมาะสม โครงสร้างเมื่อ () {} เหมือนกับสวิตช์/ตัวพิมพ์ใน Java แต่มันมีประสิทธิภาพมากกว่า - Kotlin อนุญาตให้ใช้ไม่เพียงแต่ enum หรือ int ใน 'case' แต่ยังรวมถึงช่วง สตริง หรือประเภทอ็อบเจกต์ด้วย ต้องมีความเป็นไปได้ทั้งหมดหรือมีนิพจน์อื่น ที่นี่ครอบคลุมค่า UserError ทั้งหมด

โดยใช้ view?.showEmptyNameError() (พร้อมเครื่องหมายคำถามหลังจากดู) เราได้รับการคุ้มครองจาก NullPointer มุมมองสามารถเป็นโมฆะได้ในเมธอด onDestroy และด้วยโครงสร้างนี้ จะไม่มีอะไรเกิดขึ้น

เมื่อโมเดลผู้ใช้ไม่มีข้อผิดพลาด ระบบจะแจ้งให้ UserStore บันทึก จากนั้นจึงสั่งให้ View แสดงความสำเร็จและแสดงรายละเอียด

ดังที่ได้กล่าวไว้ก่อนหน้านี้ เราต้องใช้โมเดลบางอย่าง:

 enum class UserError { EMPTY_NAME, EMPTY_SURNAME, NO_ERROR } object UserStore { fun saveUser(user: User){ //Save user somewhere: Database, SharedPreferences, send to web... } } object UserValidator { fun validateUser(user: User): UserError { with(user){ if(name.isNullOrEmpty()) return UserError.EMPTY_NAME if(surname.isNullOrEmpty()) return UserError.EMPTY_SURNAME } return UserError.NO_ERROR } }

สิ่งที่น่าสนใจที่สุดคือ UserValidator ด้วยการใช้คำอ็อบเจกต์ เราสามารถสร้างคลาสซิงเกิลตัน โดยไม่ต้องกังวลเกี่ยวกับเธรด คอนสตรัคเตอร์ส่วนตัว และอื่นๆ

สิ่งต่อไป - ในวิธี validateUser(user) มีนิพจน์ with(user) {} รหัสภายในบล็อกดังกล่าวถูกดำเนินการในบริบทของวัตถุ ผ่านด้วยชื่อและนามสกุลเป็นคุณสมบัติของผู้ใช้

ยังมีอีกสิ่งเล็กน้อย โค้ดทั้งหมดข้างต้น ตั้งแต่ enum ถึง UserValidator คำจำกัดความถูกวางไว้ในไฟล์เดียว (คำจำกัดความของคลาส User ก็อยู่ที่นี่ด้วย) Kotlin ไม่ได้บังคับให้คุณมีคลาสสาธารณะแต่ละคลาสในไฟล์เดียว (หรือชื่อคลาสเหมือนกับไฟล์) ดังนั้น หากคุณมีโค้ดสั้นๆ ที่เกี่ยวข้อง (คลาสข้อมูล ส่วนขยาย ฟังก์ชัน ค่าคงที่ Kotlin ไม่ต้องการคลาสสำหรับฟังก์ชันหรือค่าคงที่) คุณสามารถวางไว้ในไฟล์เดียวแทนที่จะกระจายผ่านไฟล์ทั้งหมดในโปรเจ็กต์

เมื่อผู้ใช้ถูกบันทึก แอพของเราควรแสดงสิ่งนั้น เราต้องการมุมมองอื่น - อาจเป็นมุมมอง Android, มุมมองที่กำหนดเอง, ส่วนย่อยหรือกิจกรรม ฉันเลือกกิจกรรม

ดังนั้น มากำหนดอินเทอร์เฟซ UserDetailsView กัน สามารถแสดงผู้ใช้ได้ แต่ควรแสดงข้อผิดพลาดเมื่อไม่มีผู้ใช้ด้วย:

 interface UserDetailsView { fun showUserDetails(user: User) fun showNoUserError() }

ถัดไป UserDetailsPresenter ควรมีคุณสมบัติผู้ใช้:

 interface UserDetailsPresenter<T: View>: Presenter<T> { var user: User? }

อินเทอร์เฟซนี้จะถูกนำมาใช้ใน UserDetailsPresenterImpl ต้องแทนที่คุณสมบัติผู้ใช้ ทุกครั้งที่กำหนดคุณสมบัตินี้ ผู้ใช้ควรได้รับการรีเฟรชในมุมมอง เราสามารถใช้ตัวตั้งค่าคุณสมบัติสำหรับสิ่งนี้:

 class UserDetailsPresenterImpl(override var view: UserDetailsView?): UserDetailsPresenter<UserDetailsView> { override var user: User? = null set(value) { field = value if(field != null){ view?.showUserDetails(field!!) } else { view?.showNoUserError() } } }

การใช้งาน UserDetailsView UserDetailsActivity นั้นง่ายมาก เหมือนเมื่อก่อน เรามีอ็อบเจ็กต์พรีเซ็นเตอร์ที่สร้างขึ้นจากการโหลดแบบ Lazy Loading ผู้ใช้ที่จะแสดงควรส่งผ่านโดยเจตนา ขณะนี้มีปัญหาเล็กน้อยประการหนึ่งเกี่ยวกับเรื่องนี้ และเราจะแก้ไขปัญหานี้ในอีกสักครู่ เมื่อเรามีผู้ใช้โดยเจตนา View จำเป็นต้องกำหนดให้กับผู้นำเสนอของพวกเขา หลังจากนั้น ผู้ใช้จะได้รับการรีเฟรชบนหน้าจอ หรือหากเป็นโมฆะ ข้อผิดพลาดจะปรากฏขึ้น (และกิจกรรมจะเสร็จสิ้น - แต่ผู้นำเสนอไม่รู้เรื่อง):

 class UserDetailsActivity: AppCompatActivity(), UserDetailsView { private val presenter: UserDetailsPresenter<UserDetailsView> by lazy { UserDetailsPresenterImpl(this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_user_details) val user = intent.getParcelableExtra<User>(USER_KEY) presenter.user = user } override fun showUserDetails(user: User) { userFullName.text = "${user.name} ${user.surname}" } override fun showNoUserError() { toast(R.string.no_user_error) finish() } override fun onDestroy() { presenter.onDestroy() } }

การส่งผ่านอ็อบเจ็กต์ผ่าน Intents ต้องการให้อ็อบเจ็กต์นี้ใช้อินเทอร์เฟซ Parcelable นี่เป็นงานที่ 'สกปรก' มาก โดยส่วนตัวแล้ว ฉันเกลียดการทำเช่นนี้เพราะผู้สร้างทั้งหมด คุณสมบัติ การออม การคืนค่า และอื่นๆ โชคดีที่มีปลั๊กอินที่เหมาะสม Parcelable สำหรับ Kotlin หลังจากติดตั้งแล้ว เราสามารถสร้าง Parcelable ได้ด้วยคลิกเดียว

สิ่งสุดท้ายที่ต้องทำคือใช้งาน showUserDetails(ผู้ใช้: ผู้ใช้) ใน MainActivity ของเรา:

 override fun showUserDetails(user: User) { startActivity<UserDetailsActivity>(USER_KEY to user) /* anko extension - starts UserDetailsActivity and pass user as USER_KEY in intent */ }

และนั่นคือทั้งหมด

แอพสาธิต Android ใน Kotlin

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

มีปัญหาด้านประสิทธิภาพในแอป Android ของคุณหรือไม่? ดูเคล็ดลับและเทคนิคการเพิ่มประสิทธิภาพเหล่านี้

อะไรต่อไป?

ในแอพของเรา Presenter จะถูกสร้างขึ้นทุกครั้งที่มีการสร้างกิจกรรม แนวทางนี้หรือตรงกันข้าม หาก Presenter ยังคงดำเนินต่อไปในอินสแตนซ์ของกิจกรรม เป็นเรื่องของการอภิปรายกันมากในอินเทอร์เน็ต สำหรับฉันมันขึ้นอยู่กับแอพ ความต้องการของมัน และผู้พัฒนา บางครั้งก็เป็นการดีที่จะทำลายผู้นำเสนอบางครั้งไม่ หากคุณตัดสินใจที่จะคงอยู่ เทคนิคที่น่าสนใจมากคือการใช้ LoaderManager สำหรับสิ่งนั้น

ดังที่ได้กล่าวไว้ก่อนหน้านี้ MVP ควรเป็นส่วนหนึ่งของสถาปัตยกรรม Clean ของ Uncle Bob นอกจากนี้ นักพัฒนาที่ดีควรใช้ Dagger เพื่อแทรกการพึ่งพาผู้นำเสนอในกิจกรรม นอกจากนี้ยังช่วยรักษา ทดสอบ และนำโค้ดกลับมาใช้ใหม่ในอนาคต ปัจจุบัน Kotlin ทำงานได้ดีกับ Dagger (ก่อนการเปิดตัวอย่างเป็นทางการ มันไม่ง่ายเลย) และกับไลบรารี Android ที่เป็นประโยชน์อื่นๆ ด้วย

สรุป

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

แน่นอน อย่างที่ฉันพูดไป ไม่มีอะไรในอุดมคติ Kotlin ยังมีข้อเสียอยู่บ้าง ปลั๊กอิน gradle เวอร์ชันใหม่ล่าสุด (ส่วนใหญ่มาจากอัลฟ่าหรือเบต้า) ใช้งานไม่ได้กับภาษานี้ หลายคนบ่นว่าเวลาในการสร้างนั้นนานกว่า Java ล้วนๆ เล็กน้อย และ apks มี MB เพิ่มเติมอยู่บ้าง แต่ Android Studio และ Gradle ยังคงได้รับการปรับปรุง และโทรศัพท์ก็มีที่ว่างสำหรับแอปมากขึ้นเรื่อยๆ นั่นเป็นเหตุผลที่ฉันเชื่อว่า Kotlin สามารถเป็นภาษาที่ดีมากสำหรับนักพัฒนา Android ทุกคน เพียงแค่ลองดูและแบ่งปันในส่วนความคิดเห็นด้านล่างสิ่งที่คุณคิด

ซอร์สโค้ดของแอปตัวอย่างมีอยู่ใน Github: github.com/tomaszczura/AndroidMVPKotlin