หัวข้อนี้มุ่งเน้นที่แง่มุมที่มีประโยชน์ที่สุดบางอย่างของภาษา Kotlin เมื่อพัฒนาแอปสำหรับ Android
ทำงานกับ Fragment
ส่วนต่อไปนี้ใช้Fragmentตัวอย่างเพื่อเน้นฟีเจอร์ที่ดีที่สุดบางส่วนของ Kotlin
การรับช่วง
คุณประกาศคลาสใน Kotlin ได้ด้วยคีย์เวิร์ด class ในตัวอย่างต่อไปนี้ LoginFragment เป็นคลาสย่อยของ Fragment คุณระบุการรับค่าได้โดยใช้โอเปอเรเตอร์ : ระหว่างคลาสย่อยกับคลาสหลัก
class LoginFragment : Fragment()
ในการประกาศคลาสนี้ LoginFragment มีหน้าที่เรียก
ตัวสร้างของคลาสระดับบน Fragment
ภายใน LoginFragment คุณสามารถลบล้างการเรียกกลับของวงจรหลายรายการเพื่อ
ตอบสนองต่อการเปลี่ยนแปลงสถานะใน Fragment หากต้องการลบล้างฟังก์ชัน ให้ใช้คีย์เวิร์ด
override ดังที่แสดงในตัวอย่างต่อไปนี้
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.login_fragment, container, false)
}
หากต้องการอ้างอิงฟังก์ชันในคลาสหลัก ให้ใช้คีย์เวิร์ด super ดังที่แสดง
ในตัวอย่างต่อไปนี้
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
ความสามารถในการเว้นว่างและการเริ่มต้น
ในตัวอย่างก่อนหน้านี้ พารามิเตอร์บางรายการในเมธอดที่ลบล้างมี
ประเภทที่ลงท้ายด้วยเครื่องหมายคำถาม ? ซึ่งบ่งชี้ว่าอาร์กิวเมนต์
ที่ส่งผ่านสำหรับพารามิเตอร์เหล่านี้อาจเป็นค่าว่าง โปรดจัดการค่าที่อาจเป็น Null อย่างปลอดภัย
ใน Kotlin คุณต้องเริ่มต้นพร็อพเพอร์ตี้ของออบเจ็กต์เมื่อประกาศออบเจ็กต์
ซึ่งหมายความว่าเมื่อคุณได้รับอินสแตนซ์ของคลาส คุณจะอ้างอิงพร็อพเพอร์ตี้ที่เข้าถึงได้ทันที
อย่างไรก็ตาม ออบเจ็กต์ View ใน Fragment จะยังไม่พร้อมที่จะขยายจนกว่าจะเรียกใช้ Fragment#onCreateView ดังนั้นคุณจึงต้องมีวิธีเลื่อนการเริ่มต้นพร็อพเพอร์ตี้สำหรับ View
lateinit ช่วยให้คุณเลื่อนการเริ่มต้นพร็อพเพอร์ตี้ได้ เมื่อใช้ lateinit
คุณควรเริ่มต้นใช้งานพร็อพเพอร์ตี้โดยเร็วที่สุด
ตัวอย่างต่อไปนี้แสดงการใช้ lateinit เพื่อกำหนดออบเจ็กต์ View ใน onViewCreated
class LoginFragment : Fragment() {
private lateinit var usernameEditText: EditText
private lateinit var passwordEditText: EditText
private lateinit var loginButton: Button
private lateinit var statusTextView: TextView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
usernameEditText = view.findViewById(R.id.username_edit_text)
passwordEditText = view.findViewById(R.id.password_edit_text)
loginButton = view.findViewById(R.id.login_button)
statusTextView = view.findViewById(R.id.status_text_view)
}
...
}
Conversion ของ SAM
คุณสามารถรอเหตุการณ์คลิกใน Android ได้โดยการใช้
OnClickListener อินเทอร์เฟซ ออบเจ็กต์ Button มีฟังก์ชัน setOnClickListener()
ที่ใช้การติดตั้งใช้งาน OnClickListener
OnClickListener มีเมธอดนามธรรมเดียวคือ onClick() ซึ่งคุณต้อง
ใช้ เนื่องจาก setOnClickListener() จะรับ OnClickListener เป็นอาร์กิวเมนต์เสมอ และเนื่องจาก OnClickListener มีเมธอดแบบนามธรรมเดียวเหมือนกันเสมอ การใช้งานนี้จึงแสดงได้โดยใช้ฟังก์ชันที่ไม่ระบุชื่อใน Kotlin กระบวนการนี้เรียกว่า
การแปลงตามรูปแบบ Single Abstract Method
หรือการแปลง SAM
การแปลง SAM จะช่วยให้โค้ดของคุณสะอาดขึ้นอย่างมาก ตัวอย่างต่อไปนี้
แสดงวิธีใช้ Conversion ของ SAM เพื่อใช้ OnClickListener สําหรับ
Button
loginButton.setOnClickListener {
val authSuccessful: Boolean = viewModel.authenticate(
usernameEditText.text.toString(),
passwordEditText.text.toString()
)
if (authSuccessful) {
// Navigate to next screen
} else {
statusTextView.text = requireContext().getString(R.string.auth_failed)
}
}
โค้ดภายในฟังก์ชันที่ไม่ระบุชื่อซึ่งส่งไปยัง setOnClickListener()
จะทํางานเมื่อผู้ใช้คลิก loginButton
ออบเจ็กต์ร่วม
ออบเจ็กต์คู่
มีกลไกในการกำหนดตัวแปรหรือฟังก์ชันที่เชื่อมโยง
ในเชิงแนวคิดกับประเภทหนึ่งๆ แต่ไม่ได้เชื่อมโยงกับออบเจ็กต์ใดออบเจ็กต์หนึ่ง ออบเจ็กต์คู่
จะคล้ายกับการใช้คีย์เวิร์ด static ของ Java สำหรับตัวแปรและเมธอด
ในตัวอย่างต่อไปนี้ TAG คือค่าคงที่ String คุณไม่จำเป็นต้องมีอินสแตนซ์ที่ไม่ซ้ำกันของ String สำหรับอินสแตนซ์แต่ละรายการของ LoginFragment ดังนั้นคุณควร
กำหนดไว้ในออบเจ็กต์คู่
class LoginFragment : Fragment() {
...
companion object {
private const val TAG = "LoginFragment"
}
}
คุณกำหนด TAG ที่ระดับบนสุดของไฟล์ได้ แต่ไฟล์อาจมีตัวแปร ฟังก์ชัน และคลาสจำนวนมากที่กำหนดไว้ที่ระดับบนสุดด้วย ออบเจ็กต์คู่ช่วยเชื่อมต่อตัวแปร ฟังก์ชัน และคำจำกัดความของคลาสโดยไม่ต้องอ้างอิงอินสแตนซ์ใดๆ ของคลาสนั้น
การมอบสิทธิ์ของพร็อพเพอร์ตี้
เมื่อเริ่มต้นพร็อพเพอร์ตี้ คุณอาจทำซ้ำรูปแบบที่พบบ่อยกว่าของ Android บางรูปแบบ เช่น การเข้าถึง ViewModel ภายใน Fragment คุณสามารถใช้ไวยากรณ์การมอบสิทธิ์พร็อพเพอร์ตี้ของ Kotlin เพื่อหลีกเลี่ยงโค้ดที่ซ้ำกันมากเกินไปได้
private val viewModel: LoginViewModel by viewModels()
การมอบสิทธิ์พร็อพเพอร์ตี้จะมีการติดตั้งใช้งานทั่วไปที่คุณนำกลับมาใช้ใหม่ได้
ทั่วทั้งแอป Android KTX มีการมอบสิทธิ์พร็อพเพอร์ตี้บางอย่างให้คุณ
เช่น viewModels จะดึงข้อมูล ViewModel ที่กำหนดขอบเขตไว้สำหรับ
Fragment ปัจจุบัน
การมอบสิทธิ์พร็อพเพอร์ตี้ใช้การสะท้อน ซึ่งจะเพิ่มค่าใช้จ่ายด้านประสิทธิภาพบางอย่าง ข้อแลกเปลี่ยนคือไวยากรณ์ที่กระชับซึ่งช่วยประหยัดเวลาในการพัฒนา
ความสามารถในการเว้นว่าง
Kotlin มีกฎเกี่ยวกับ Nullability ที่เข้มงวดซึ่งรักษาความปลอดภัยของประเภทในแอปของคุณ
โดยค่าเริ่มต้น การอ้างอิงไปยังออบเจ็กต์ใน Kotlin จะต้องไม่มีค่า Null
หากต้องการกำหนดค่า Null ให้กับตัวแปร คุณต้องประกาศประเภทตัวแปรที่ยอมรับค่า Null
โดยการเพิ่ม ? ที่ท้ายประเภทฐาน
ตัวอย่างเช่น นิพจน์ต่อไปนี้ไม่ถูกต้องใน Kotlin name มีประเภท
String และไม่ใช่ค่าที่กำหนดให้เป็น Null ได้
val name: String = null
หากต้องการอนุญาตค่า Null คุณต้องใช้ประเภท String ที่อนุญาตให้เป็น Null ได้ String? ดังที่แสดงในตัวอย่างต่อไปนี้
val name: String? = null
ความสามารถในการทำงานร่วมกัน
กฎที่เข้มงวดของ Kotlin ทำให้โค้ดปลอดภัยและกระชับยิ่งขึ้น กฎเหล่านี้จะช่วยลดโอกาสที่จะเกิดNullPointerExceptionซึ่งอาจทำให้แอปของคุณ
ขัดข้อง นอกจากนี้ ยังช่วยลดจำนวนการตรวจสอบค่า Null ที่คุณต้องทำในโค้ดด้วย
บ่อยครั้งที่คุณต้องเรียกใช้โค้ดที่ไม่ใช่ Kotlin เมื่อเขียนแอป Android เนื่องจาก API ของ Android ส่วนใหญ่เขียนด้วยภาษาโปรแกรม Java
Nullability เป็นส่วนสำคัญที่ Java และ Kotlin มีลักษณะการทำงานแตกต่างกัน Java มีความเข้มงวดน้อยกว่า ในเรื่องไวยากรณ์การกำหนดค่า Null
ตัวอย่างเช่น คลาส Account มีพร็อพเพอร์ตี้ 2-3 รายการ รวมถึงพร็อพเพอร์ตี้ String
ที่ชื่อ name Java ไม่มีกฎของ Kotlin เกี่ยวกับค่า Null
แต่จะใช้คำอธิบายประกอบเกี่ยวกับค่า Null ที่ไม่บังคับเพื่อประกาศอย่างชัดเจน
ว่าคุณกำหนดค่า Null ได้หรือไม่
เนื่องจากเฟรมเวิร์ก Android เขียนด้วยภาษา Java เป็นหลัก คุณจึงอาจพบสถานการณ์นี้เมื่อเรียกใช้ API โดยไม่มีคำอธิบายประกอบเกี่ยวกับค่า Null
ประเภทแพลตฟอร์ม
หากคุณใช้ Kotlin เพื่ออ้างอิงสมาชิก name ที่ไม่ได้ใส่คำอธิบายประกอบซึ่งกำหนดไว้ในคลาส Account ของ Java คอมไพเลอร์จะไม่ทราบว่า String แมปกับ String หรือ String? ใน Kotlin ความคลุมเครือนี้แสดงผ่านประเภทแพลตฟอร์ม String!
String! ไม่มีความหมายพิเศษสำหรับคอมไพเลอร์ Kotlin String! สามารถแสดงถึง
ทั้ง String หรือ String? และคอมไพเลอร์จะช่วยให้คุณกำหนดค่าของ
ประเภทใดก็ได้ โปรดทราบว่าคุณอาจทำให้เกิด NullPointerException หากแสดงประเภทเป็น String และกำหนดค่าเป็น Null
หากต้องการแก้ไขปัญหานี้ คุณควรใช้คำอธิบายประกอบเกี่ยวกับค่า Null ทุกครั้งที่เขียนโค้ดใน Java คำอธิบายประกอบเหล่านี้ช่วยทั้งนักพัฒนาแอป Java และ Kotlin
ตัวอย่างเช่น นี่คือคลาส Account ตามที่กำหนดไว้ใน Java
public class Account implements Parcelable {
public final String name;
public final String type;
private final @Nullable String accessId;
...
}
ตัวแปรสมาชิกตัวใดตัวหนึ่ง accessId มีคำอธิบายประกอบด้วย @Nullable
ซึ่งระบุว่าตัวแปรดังกล่าวสามารถมีค่าเป็น Null ได้ จากนั้น Kotlin จะถือว่า accessId
เป็น String?
หากต้องการระบุว่าตัวแปรต้องไม่เป็น Null ให้ใช้คำอธิบายประกอบ @NonNull ดังนี้
public class Account implements Parcelable {
public final @NonNull String name;
...
}
ในสถานการณ์นี้ ระบบจะถือว่า name เป็น String ที่ไม่เป็น Null ใน Kotlin
คำอธิบายประกอบการยอมรับค่า Null จะรวมอยู่ใน Android API ใหม่ทั้งหมดและ Android API ที่มีอยู่หลายรายการ ไลบรารี Java หลายรายการได้เพิ่มคำอธิบายประกอบเกี่ยวกับค่า Null เพื่อให้รองรับทั้งนักพัฒนาซอฟต์แวร์ Kotlin และ Java ได้ดียิ่งขึ้น
การจัดการความสามารถในการเว้นว่าง
หากไม่แน่ใจเกี่ยวกับประเภท Java คุณควรพิจารณาว่าประเภทนั้นเป็นประเภทที่กำหนดให้เป็น Null ได้
ตัวอย่างเช่น ระบบไม่ได้ใส่คำอธิบายประกอบให้กับสมาชิก name ของคลาส Account ดังนั้นคุณ
ควรสมมติว่าสมาชิกดังกล่าวเป็น String? ที่อนุญาตให้เป็นค่าว่างได้
หากต้องการตัด name เพื่อให้ค่าไม่มีช่องว่างนำหน้าหรือต่อท้าย คุณสามารถใช้ฟังก์ชัน trim ของ Kotlin ได้ คุณตัดString?ได้อย่างปลอดภัยด้วยวิธีต่างๆ วิธีหนึ่งคือการใช้ตัวดำเนินการยืนยันว่าไม่ใช่ค่า Null !! ดังที่แสดงในตัวอย่างต่อไปนี้
val account = Account("name", "type")
val accountName = account.name!!.trim()
ตัวดำเนินการ !! จะถือว่าทุกอย่างทางด้านซ้ายมือเป็นค่าที่ไม่ใช่ Null ดังนั้นในกรณีนี้ คุณจะถือว่า name เป็น String ที่ไม่ใช่ Null หากผลลัพธ์ของ
นิพจน์ทางด้านซ้ายเป็น Null แอปจะแสดง NullPointerException
โอเปอเรเตอร์นี้ใช้งานง่ายและรวดเร็ว แต่ควรใช้อย่างระมัดระวังเนื่องจากอาจทำให้NullPointerExceptionกลับมาปรากฏในโค้ดอีกครั้ง
ตัวเลือกที่ปลอดภัยกว่าคือการใช้โอเปอเรเตอร์การเรียกที่ปลอดภัย ?. ดังที่แสดงใน
ตัวอย่างต่อไปนี้
val account = Account("name", "type")
val accountName = account.name?.trim()
เมื่อใช้ตัวดำเนินการเรียกอย่างปลอดภัย หาก name ไม่ใช่ค่าว่าง ผลลัพธ์ของ
name?.trim() จะเป็นค่าชื่อที่ไม่มีช่องว่างนำหน้าหรือต่อท้าย หาก name เป็น Null ผลลัพธ์ของ name?.trim() จะเป็น null ซึ่งหมายความว่าแอปของคุณจะไม่มีทางส่ง NullPointerException เมื่อดำเนินการกับคำสั่งนี้
แม้ว่าตัวดำเนินการเรียกอย่างปลอดภัยจะช่วยให้คุณไม่ต้องกังวลเรื่อง NullPointerException
แต่ก็ส่งค่า Null ไปยังคำสั่งถัดไป คุณสามารถจัดการกรณีค่า Null ได้ทันทีโดยใช้ตัวดำเนินการ Elvis (?:) ดังที่แสดงในตัวอย่างต่อไปนี้
val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"
หากผลลัพธ์ของนิพจน์ทางด้านซ้ายของตัวดำเนินการ Elvis เป็น
null ระบบจะกำหนดค่าทางด้านขวาให้กับ accountName
เทคนิคนี้มีประโยชน์ในการระบุค่าเริ่มต้นที่มิฉะนั้นจะเป็นค่า Null
นอกจากนี้ คุณยังใช้ตัวดำเนินการ Elvis เพื่อออกจากฟังก์ชันก่อนเวลาอันควรได้ด้วย ดังที่แสดง ในตัวอย่างต่อไปนี้
fun validateAccount(account: Account?) {
val accountName = account?.name?.trim() ?: "Default name"
// account cannot be null beyond this point
account ?: return
...
}
การเปลี่ยนแปลง API ของ Android
Android API กำลังเป็นมิตรกับ Kotlin มากขึ้นเรื่อยๆ API ที่ใช้กันมากที่สุดของ Android หลายรายการ ซึ่งรวมถึง AppCompatActivity และ Fragment มี
คำอธิบายประกอบเกี่ยวกับค่า Null และการเรียกใช้บางอย่าง เช่น Fragment#getContext มี
ทางเลือกอื่นที่เหมาะกับ Kotlin มากกว่า
ตัวอย่างเช่น การเข้าถึง Context ของ Fragment มักจะไม่เป็นค่าว่าง
เนื่องจากการเรียกส่วนใหญ่ที่คุณทำใน Fragment จะเกิดขึ้นขณะที่ Fragment
แนบอยู่กับ Activity (คลาสย่อยของ Context) อย่างไรก็ตาม
Fragment#getContext ไม่ได้แสดงค่าที่ไม่ใช่ค่าว่างเสมอไป เนื่องจากมี
สถานการณ์ที่ Fragment ไม่ได้แนบอยู่กับ Activity ดังนั้น ประเภทการคืนค่าของ Fragment#getContext จึงเป็นค่าที่กำหนดให้เป็น Null ได้
เนื่องจาก Context ที่แสดงผลจาก Fragment#getContext เป็นค่าที่เว้นว่างได้ (และมี
คำอธิบายประกอบเป็น @Nullable) คุณจึงต้องถือว่าเป็น Context? ในโค้ด Kotlin
ซึ่งหมายถึงการใช้ตัวดำเนินการที่กล่าวถึงก่อนหน้านี้กับความสามารถในการเป็นค่าว่างก่อนที่จะเข้าถึงพร็อพเพอร์ตี้และฟังก์ชัน สำหรับ
สถานการณ์บางอย่างเหล่านี้ Android มี API ทางเลือกที่ให้ความสะดวกนี้
Fragment#requireContext เช่น จะแสดงผล Context ที่ไม่ใช่ Null และจะแสดง
IllegalStateException หากเรียกใช้เมื่อ Context เป็น Null วิธีนี้จะช่วยให้คุณถือว่า Context ที่ได้เป็นค่าที่ไม่ใช่ Null โดยไม่ต้องใช้
โอเปอเรเตอร์การเรียกที่ปลอดภัยหรือวิธีแก้ปัญหา
การเริ่มต้นพร็อพเพอร์ตี้
พร็อพเพอร์ตี้ใน Kotlin จะไม่ได้รับการเริ่มต้นโดยค่าเริ่มต้น ต้องเริ่มต้น เมื่อเริ่มต้นคลาสที่ครอบคลุม
คุณเริ่มต้นใช้งานพร็อพเพอร์ตี้ได้หลายวิธี ตัวอย่างต่อไปนี้
แสดงวิธีเริ่มต้นตัวแปร index โดยการกำหนดค่าให้กับตัวแปรในการประกาศคลาส
class LoginFragment : Fragment() {
val index: Int = 12
}
นอกจากนี้ คุณยังกำหนดการเริ่มต้นนี้ในบล็อกการเริ่มต้นได้ด้วย
class LoginFragment : Fragment() {
val index: Int
init {
index = 12
}
}
ในตัวอย่างด้านบน index จะเริ่มต้นเมื่อสร้าง LoginFragment
อย่างไรก็ตาม คุณอาจมีพร็อพเพอร์ตี้บางรายการที่เริ่มต้นไม่ได้ในระหว่างการสร้างออบเจ็กต์ เช่น คุณอาจต้องการอ้างอิง View จากภายใน
Fragment ซึ่งหมายความว่าต้องขยายเลย์เอาต์ก่อน การขยายขนาดจะไม่เกิดขึ้นเมื่อสร้าง Fragment แต่จะเพิ่มขึ้นเมื่อโทรหา
Fragment#onCreateView
วิธีหนึ่งในการจัดการกับสถานการณ์นี้คือการประกาศมุมมองเป็น Nullable และ เริ่มต้นมุมมองโดยเร็วที่สุด ดังที่แสดงในตัวอย่างต่อไปนี้
class LoginFragment : Fragment() {
private var statusTextView: TextView? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
statusTextView = view.findViewById(R.id.status_text_view)
statusTextView?.setText(R.string.auth_failed)
}
}
แม้ว่าการดำเนินการนี้จะทำงานตามที่คาดไว้ แต่ตอนนี้คุณต้องจัดการความสามารถในการเป็นค่าว่างของ View
ทุกครั้งที่อ้างอิง วิธีแก้ปัญหาที่ดีกว่าคือการใช้ lateinit สำหรับView
การเริ่มต้น ดังที่แสดงในตัวอย่างต่อไปนี้
class LoginFragment : Fragment() {
private lateinit var statusTextView: TextView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
statusTextView = view.findViewById(R.id.status_text_view)
statusTextView.setText(R.string.auth_failed)
}
}
คีย์เวิร์ด lateinit ช่วยให้คุณหลีกเลี่ยงการเริ่มต้นพร็อพเพอร์ตี้เมื่อสร้างออบเจ็กต์ได้ หากมีการอ้างอิงพร็อพเพอร์ตี้ก่อนที่จะมีการเริ่มต้น Kotlin จะแสดง UninitializedPropertyAccessException ดังนั้นโปรด
เริ่มต้นพร็อพเพอร์ตี้โดยเร็วที่สุด