ใช้รูปแบบ Kotlin ทั่วไปกับ Android

หัวข้อนี้มุ่งเน้นที่บางแง่มุมที่มีประโยชน์ที่สุดของภาษา Kotlin เมื่อพัฒนาสำหรับ Android

ทำงานกับ Fragment

ส่วนต่อไปนี้ใช้ตัวอย่าง Fragment เพื่อไฮไลต์ ที่ดีที่สุด

การรับค่า

คุณประกาศคลาสใน Kotlin ได้ด้วยคีย์เวิร์ด class ในรายการต่อไปนี้ เช่น LoginFragment เป็นคลาสย่อยของ Fragment คุณสามารถระบุ การรับช่วงค่าโดยใช้โอเปอเรเตอร์ : ระหว่างคลาสย่อยและคลาสย่อย:

class LoginFragment : Fragment()

ในการประกาศของชั้นเรียนนี้ LoginFragment จะเป็นผู้รับผิดชอบการเรียกใช้การเรียก ตัวสร้างของซูเปอร์คลาส Fragment

ภายใน LoginFragment คุณสามารถลบล้างจํานวน Callback ของวงจรเป็น ตอบสนองต่อการเปลี่ยนแปลงสถานะใน 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)
}

ความสามารถในการเว้นว่างและการเริ่มต้น

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

ใน 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 กระบวนการนี้เรียกว่า การแปลงวิธีนามธรรมเดี่ยว หรือ SAM Conversion

Conversion ของ 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 มีกฎความสามารถในการเว้นว่างที่เข้มงวดซึ่งจะรักษาระดับความปลอดภัยตามประเภททั้งหมด แอปของคุณ ใน Kotlin การอ้างอิงออบเจ็กต์ต้องไม่มีค่า Null "ค่าเริ่มต้น" หากต้องการกำหนดค่า Null ให้กับตัวแปร คุณต้องประกาศ nullable ประเภทตัวแปรได้โดยการเพิ่ม ? ต่อท้ายประเภทฐาน

ตัวอย่างเช่น นิพจน์ต่อไปนี้ใน Kotlin ผิดกฎหมาย name เป็นประเภท String และไม่สามารถเป็นค่าว่าง

val name: String = null

หากต้องการอนุญาตค่า Null คุณต้องใช้ประเภท String ที่ไม่มีข้อมูล ซึ่งก็คือ String? เป็น ที่แสดงในตัวอย่างต่อไปนี้

val name: String? = null

ความสามารถในการทำงานร่วมกัน

กฎที่เข้มงวดของ Kotlin ทำให้โค้ดของคุณปลอดภัยและกระชับยิ่งขึ้น กฎเหล่านี้ต่ำกว่า โอกาสในการมี NullPointerException ที่จะทำให้แอป ขัดข้อง นอกจากนี้ ยังช่วยลดจำนวนของการตรวจสอบ Null ที่คุณต้องดำเนินการในบัญชี โค้ด

บ่อยครั้งคุณต้องเรียกใช้โค้ดที่ไม่ใช่ Kotlin เมื่อเขียนแอป Android ด้วย เช่น API ของ Android ส่วนใหญ่เขียนด้วยภาษาโปรแกรม Java

ความสามารถในการเว้นว่างคือส่วนสำคัญที่ Java และ Kotlin มีลักษณะการทำงานที่แตกต่างกัน Java มีราคาน้อยกว่า ที่เข้มงวดด้วยไวยากรณ์ที่ไม่มีข้อมูล

ตัวอย่างเช่น คลาส Account มีพร็อพเพอร์ตี้หลายรายการ รวมถึง String ชื่อ name Java ไม่มีกฎของ Kotlin เกี่ยวกับความสามารถในการเว้นว่าง การพึ่งพาคำอธิบายประกอบที่เป็นค่าว่างซึ่งไม่บังคับ เพื่อประกาศ คุณสามารถกำหนดค่า Null ได้หรือไม่

เนื่องจากเฟรมเวิร์ก Android นั้นเขียนโดยใช้ Java เป็นหลัก คุณจึงอาจพบกับ ในสถานการณ์นี้เมื่อเรียกใช้ API โดยไม่มีคำอธิบายประกอบความสามารถในการเว้นว่าง

ประเภทแพลตฟอร์ม

หากคุณใช้ Kotlin เพื่ออ้างอิงสมาชิก name ที่ไม่มีคำอธิบายประกอบซึ่งกำหนดไว้ใน คลาส Java Account คอมไพเลอร์จะไม่ทราบว่า String จับคู่กับ String หรือ String? ใน Kotlin ความคลุมเครือนี้จะแสดงผ่าน ประเภทแพลตฟอร์ม, String!

String! ไม่ได้มีความหมายพิเศษต่อคอมไพเลอร์ Kotlin String! สามารถนำเสนอ String หรือ String? และคอมไพเลอร์จะให้คุณกำหนดค่า ประเภทใดก็ได้ โปรดทราบว่าคุณอาจเสี่ยงต่อการส่ง NullPointerException หาก แสดงประเภทเป็น String และกําหนดค่า 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?

หากต้องการระบุว่าตัวแปรต้องไม่เป็นค่าว่าง ให้ใช้คำอธิบายประกอบ @NonNull ดังนี้

public class Account implements Parcelable {
    public final @NonNull String name;
    ...
}

ในสถานการณ์นี้ name จะถือเป็น String ที่เป็นค่าว่างไม่ได้ใน Kotlin

คำอธิบายประกอบ Nullability จะรวมอยู่ใน Android API ใหม่ทั้งหมดและหลาย API ที่มีอยู่แล้ว Android API ไลบรารี Java จำนวนมากได้เพิ่มคำอธิบายประกอบ Nullability เพื่อให้ รองรับทั้งนักพัฒนาซอฟต์แวร์ Kotlin และ Java

การจัดการความสามารถในการเว้นว่าง

หากคุณไม่แน่ใจเกี่ยวกับประเภท Java คุณควรพิจารณาว่าประเภทนั้นเป็นโมฆะ ยกตัวอย่างเช่น สมาชิก name ของชั้นเรียน Account ไม่มีการใส่คำอธิบายประกอบ คุณจึง ควรถือว่าเป็น String ที่ไม่มีข้อมูล

หากต้องการตัด name เพื่อให้ค่าไม่มีเครื่องหมายนำหน้าหรือ ต่อท้ายด้วยช่องว่าง คุณสามารถใช้ฟังก์ชัน trim ของ Kotlin ได้ คุณสามารถตัด String? หลากหลายวิธี หนึ่งในวิธีเหล่านี้คือการใช้แอตทริบิวต์ not-null โอเปอเรเตอร์การยืนยัน !! ดังที่แสดงในตัวอย่างต่อไปนี้

val account = Account("name", "type")
val accountName = account.name!!.trim()

ตัวดำเนินการ !! จะถือว่าทุกอย่างที่อยู่ทางซ้ายมือเป็นแบบไม่ใช่ค่าว่าง ดังนั้นใน ในกรณีนี้ คุณกำลังถือว่า name เป็น String ที่ไม่เป็นค่าว่าง หากผลลัพธ์ของ นิพจน์ไปทางซ้ายมีค่าเป็น null แสดงว่าแอปของคุณแสดง NullPointerException โอเปอเรเตอร์นี้รวดเร็วและง่ายดาย แต่ควรใช้งานอย่างจำกัดเนื่องจาก แนะนำอินสแตนซ์ของ NullPointerException อีกครั้งในโค้ดของคุณ

ตัวเลือกที่ปลอดภัยกว่าคือการใช้โอเปอเรเตอร์ Safe-Call, ?. ดังที่แสดงใน ตัวอย่างต่อไปนี้

val account = Account("name", "type")
val accountName = account.name?.trim()

การใช้โอเปอเรเตอร์ Safe-call หาก name เป็นค่าว่าง ผลลัพธ์จะเป็น name?.trim() คือค่าชื่อที่ไม่มีช่องว่างนำหน้าหรือต่อท้าย ถ้า name เป็นค่าว่าง จากนั้นผลลัพธ์ของ name?.trim() จะเป็น null ซึ่งหมายความว่า แอปของคุณต้องไม่ส่ง NullPointerException เมื่อเรียกใช้คำสั่งนี้

แม้ว่าโอเปอเรเตอร์การโทรช่วยให้คุณไม่ต้องเสียเวลาจากNullPointerExceptionที่อาจเกิดขึ้น จะส่งค่า Null ไปยังคำสั่งถัดไป คุณจัดการค่า Null แทนได้ กรณีทันทีโดยใช้โอเปอเรเตอร์ Elvis (?:) ดังที่แสดงใน ตัวอย่าง:

val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"

ถ้าผลลัพธ์ของนิพจน์ทางด้านซ้ายมือของโอเปอเรเตอร์ของเอลวิสคือ null ค่าทางด้านขวามือจะกำหนดให้กับ accountName ช่วงเวลานี้ มีประโยชน์สำหรับการระบุค่าเริ่มต้นที่อาจเป็นค่าว่าง

หรือจะใช้โอเปอเรเตอร์ Elvis เพื่อออกจากฟังก์ชันก่อนก็ได้ ตามที่แสดง ในตัวอย่างต่อไปนี้

fun validateAccount(account: Account?) {
    val accountName = account?.name?.trim() ?: "Default name"

    // account cannot be null beyond this point
    account ?: return

    ...
}

การเปลี่ยนแปลง API ของ Android

API ของ Android มีความเป็นมิตรกับ Kotlin มากขึ้น Android หลายรุ่น API ที่พบบ่อยที่สุด รวมถึง AppCompatActivity และ Fragment มี คำอธิบายประกอบที่ไม่มีข้อมูล และการเรียกบางรายการ เช่น Fragment#getContext ทางเลือกอื่นๆ ที่ใช้กับ Kotlin ได้

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

เนื่องจาก Context ที่แสดงผลจาก Fragment#getContext เป็นค่าว่าง (และ มีคำอธิบายประกอบเป็น @Nullable) คุณจะต้องตั้งค่าเป็น Context? ในโค้ด Kotlin ซึ่งหมายถึงการใช้โอเปอเรเตอร์รายการใดรายการหนึ่งที่กล่าวถึงก่อนหน้านี้เพื่อจัดการกับ ความสามารถในการเว้นว่างก่อนที่จะเข้าถึงคุณสมบัติและฟังก์ชัน สำหรับเพลงเหล่านี้บางรายการ อย่างเช่น Android มี API อื่นๆ ที่ให้ความสะดวกสบายนี้ เช่น Fragment#requireContext แสดงผล Context ที่ไม่เป็นค่าว่าง และส่ง IllegalStateException หากเรียกใช้เมื่อ Context จะเป็นค่าว่าง ด้วยวิธีนี้ คุณสามารถถือว่า Context ที่เป็นผลลัพธ์ไม่เป็นค่าว่างได้โดยไม่ต้อง ผู้ให้บริการที่ปลอดภัยหรือวิธีแก้ปัญหาเบื้องต้น

การเริ่มต้นพร็อพเพอร์ตี้

พร็อพเพอร์ตี้ใน 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

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

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 ดังนั้นอย่าลืม เริ่มต้นพร็อพเพอร์ตี้ของคุณโดยเร็วที่สุด