ในบทความนี้จะมาพูดถึงการสร้าง Instance สำหรับ Fragment เพราะถึงแม้ว่า Fragment จะเป็นแค่คลาสตัวหนึ่งเหมือนกับคลาสทั่ว ๆ ไป แต่ก็มีเงื่อนไขในการสร้าง Instance อยู่นะ
บทความในซีรีย์เดียวกัน
- Fragment ตอนที่ 1 - Introduction
- Fragment ตอนที่ 2 - Get Started
- Fragment ตอนที่ 3 - Fragment Creation [Now Reading]
- รู้จักกับ FragmentTransaction สำหรับการแสดง Fragment [ตอนที่ 1]
- รู้จักกับ FragmentTransaction สำหรับการแสดง Fragment [ตอนที่ 2]
- Lifecycle ของ Fragment (Fragment Lifecycle)
- วิธีการรับส่งข้อมูลของ Fragment
- มาทำ View Pager กันเถิดพี่น้อง~ [ตอนที่ 1]
- มาทำ View Pager กันเถิดพี่น้อง~ [ตอนที่ 2]
- เพิ่มลูกเล่นให้กับ View Pager ด้วย Page Transformer
Fragment จะมี Empty Constructor เสมอ
เพราะ Fragment เป็น Component ตัวหนึ่งที่มีโอกาสโดนระบบแอนดรอยด์เคลียร์ทิ้งชั่วคราวเพื่อคืน Memory ได้เช่นเดียวกับ Activity จึงทำให้ระบบแอนดรอยด์บังคับว่า Constuctor ของ Fragment จะต้องมี Empty Constructor อยู่ด้วย
class HomeFragment : Fragment() { ... }
ถ้านักพัฒนาสร้าง Fragment แบบไม่มี Empty Constructor อยู่ด้วย ตอนสร้าง Instance ขึ้นมาจะทำงานได้ปกติ แต่แอปจะพังทันทีในตอนที่ Recreate Fragment ตัวนั้นขึ้นมาใหม่
Fragment สามารถทำ Overload Constructor โดยมี Empty Constructor ได้เช่นกัน แต่ส่วนใหญ่จะไม่ทำแบบนั้นกัน และจะใช้วิธีในหัวข้อถัดไปแทน
ส่งข้อมูลเริ่มต้นให้ Fragment ด้วย Bundle แทนการสร้าง Constructor Parameter
ในบางครั้งนักพัฒนาอาจจะต้องการส่งข้อมูลเริ่มต้นเพื่อให้ Fragment เริ่มทำงานตามที่ต้องการได้ ซึ่งการส่งข้อมูลแบบนี้จะใช้วิธีส่งผ่าน Argument ของ Fragment ในรูปของ Bundle
val fragment = HomeFragment().apply {
arguments = Bundle().apply {
putString("productId", /* ... */)
putInt("amount", /* ... */)
}
}
เหมือนกับตอนที่แนบข้อมูลใน Intent เพื่อส่งให้ Activity เลยใช่มั้ยล่ะ
และ Fragment ก็จะดึงข้อมูลจาก Argument ดังกล่าวเพื่อนำไปใช้งานต่อ
class HomeFragment : Fragment() {
private val productId: String? by lazy { arguments?.getString("productId") }
private val amount: Int? by lazy { arguments?.getInt("amount") }
/* ... */
}
การส่งข้อมูลด้วยวิธีนี้จะมีความต่อการทำงานของ Fragment ในตอนที่ Fragment ถูกชทำลายเพื่อคืน Memory ให้กับระบบแอนดรอยด์ชั่วคราว เพราะข้อมูลใน Argument ของ Fragment จะถูกเก็บไว้ในระบบแอนดรอยด์และคืนให้ตอนที่ Recreate Fragment ทำให้ข้อมูลดังกล่าวไม่มีทางหาย ในขณะที่การสร้าง Constructor Parameter จะเจอปัญหาว่าข้อมูลจะหายไปในระหว่างนั้น
ใช้ Function ใน Companion Object เพื่อให้ง่ายต่อการสร้าง Fragment
จากตัวอย่างก่อนหน้าจะเห็นว่าการแนบข้อมูลเริ่มต้นจะส่งผ่าน Arguments ของ Fragment ที่มีโค้ดประมาณหนึ่ง จึงนิยมสร้าง Function ไว้ใน Companion Object ของ Fragment เพื่อรวมคำสั่งในการสร้าง Fragment แทน
class HomeFragment : Fragment() {
/* ... */
companion object {
fun newInstance(productId: String, amount: Int) = HomeFragment().apply {
arguments = Bundle().apply {
putString("productId", productId)
putInt("amount", amount)
}
}
}
}
ทำให้เวลาสร้าง Instance ของ Fragment จากที่ใดก็ตาม จะเรียกผ่านคำสั่ง newInstance
ที่มีความกระชับกว่าได้เลย
val productId: String = /* ... */
val amount: Int = /* ... */
val fragment = HomeFragment.newInstance(productId, amount)
ในกรณีที่ใช้ Jetpack Navigation จะกำหนดค่าผ่าน Destination Argument ของ Jetpack Navigation ได้เลย ไม่ต้องสร้าง Function แบบนี้ขึ้นมาเอง
Fragment ก็รองรับ State Changes
Fragment รองรับ State Changes แบบเดียวกับ Activity ไม่ว่าจะเป็น Configuration Changes หรือ Activity Recreation ก็ตาม จึงทำให้ Fragment สามารถกลับมาทำงานต่อจากเดิมได้
ค่าใด ๆ ที่ประกาศไว้ใน Fragment ก็จะต้องจัดการแบบเดียวกับ Activity ด้วยเช่นกัน
และสิ่งที่ควรระวังก็คือ ถ้านักพัฒนาสร้าง Fragment ผ่าน Fragment Manager อย่าเผลอไปสร้างซ้ำในตอนที่เกิด State Changes เด็ดขาด ไม่เช่นนั้นจะได้ Fragment ซ้ำขึ้นมาอีกตัวโดยไม่รู้ตัว
ยกตัวอย่างเช่น ถ้าสร้าง Fragment ผ่าน Fragment Manager ตอน onCreate
ของ Activity ก็ให้สร้างเฉพาะตอนที่ Activity ถูกสร้างขึ้นมาครั้งแรกเท่านั้น
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
if (savedInstanceState == null) {
supportFragmentManager.commit {
replace(/* ... */)
addToBackStack(null)
}
}
}
/* ... */
}
โดยใช้วิธีเช็คจาก savedInstanceState
นั่นเอง ถ้ามีค่าเป็น null
ก็ให้สร้าง Fragment ขึ้นมาใหม่ แต่ถ้าไม่ใช่ก็ไม่จำเป็นต้องทำอะไร เพราะมี Fragment ตัวเก่าอยู่แล้ว
สรุป
เพื่อให้ Fragment รองรับการ Recreate ใหม่ได้ ทำให้ระบบแอนดรอยด์ออกแบบให้ Fragment ต้องมี Empty Constructor ด้วยเสมอ และการส่งข้อมูลในตอนที่สร้าง Instance ของ Fragment ตัวนั้น ก็จะให้ส่งผ่าน Argument ของ Fragment แทน
ทั้งหมดนี้ก็เพื่อให้ Fragment รองรับการทำงานของระบบแอนดรอยด์ในทุกเงื่อนไขได้อย่างถูกต้องนั่นเอง