Android State Changes - State Handling in Fragment
Fragment ก็เป็นหนึ่งใน Component ที่มี Lifecycle และได้รับผลกระทบจาก State Changes ไม่ต่างจาก Activity ทำให้นักพัฒนาจำเป็นต้องจัดการกับข้อมูลจำพวก UI State เพื่อให้ Fragment ทำงานต่อได้อย่างเหมาะสม
ในปัจจุบันจะไม่นิยมเก็บข้อมูลจำพวก UI State ไว้ใน Fragment โดยตรง ควรเก็บไว้ใน ViewModel ตามรูปแบบของ App Architecture ที่ทีมแอนดรอยด์แนะนำ
บทความในชุดเดียวกัน
- Introduction
- Configuration Changes
- Process Recreation
- State Handing in Activity
- State Handing in Fragment [Now Reading]
- State Handing in View
- State Handing in ViewModel
- State Handing in Compose
Fragment ก็ถูก Recreate ได้เหมือนกับ Activity
Fragment เป็น Component ที่ทำงานอยู่บน Activity อีกทีหนึ่ง ดังนั้นเมื่อเกิด State Changes และส่งผลให้ Activity ทำการ Recreate ใหม่ ก็หมายความว่า Fragment ก็จะต้อง Recreate ใหม่ด้วยเช่นกัน
และทีมแอนดรอยด์ก็ได้ออกแบบการทำงานของ Fragment ให้เอื้อต่อการทำ State Handling ในรูปแบบที่คล้ายกับ Activity ไว้ให้แล้ว แต่จะไม่ได้ทำงานเหมือนกันทั้งหมด เพราะ Fragment เป็น Component ที่สามารถ Attach หรือ Detach จาก Activity เมื่อไรก็ได้
การทำ State Handling ใน Fragment
เมื่อลองย้อนกลับไปดูลำดับการทำงานของ State Handling ของ Activity ก็จะเห็นว่าใน Activity มี Method ที่ชื่อว่า onSaveInstanceState
และ onRestoreInstanceState
ไว้ให้ใช้งานอยู่แล้ว
แต่สำหรับ Fragment ที่ไม่ได้มี Lifecycle เหมือนกับ Activity โดยตรง จึงถูกออกแบบมาให้นักพัฒนาต้องเก็บข้อมูลใน Method ที่ชื่อว่า onSaveInstanceState
(เก็บข้อมูลไว้ในตัวแปรที่ชื่อว่า outState
ที่ส่งเข้ามาให้ใน Method เหมือนกับใน Activity) และคืนข้อมูลในระหว่าง onCreate
, onCreateView
, หรือ onViewCreated
ก็ได้ เพราะ Method เหล่านี้จะมีตัวแปรที่ชื่อ savedInstanceState
ส่งเข้ามาให้เพื่อคืนข้อมูลกลับมาหลังจาก Fragment ถูก Recreate เสร็จเรียบร้อยแล้ว
// HomeFragment.kt
class HomeFragment: Fragment() {
// Save
override fun onSaveInstanceState(outState: Bundle) { /* ... */ }
// Restore
override fun onCreate(savedInstanceState: Bundle?) { /* ... */ }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View { /* ... */ }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { /* ... */ }
}
ในการคืนข้อมูลหลังจากเกิด State Changes นักพัฒนาจะต้องตัดสินใจเองว่าจะทำใน Method ไหน เพราะขึ้นอยู่กับการทำงานของ Fragment ที่นักพัฒนาจะต้องเลือกให้เหมาะสม
และถ้าต้องการรู้ว่า Fragment ตัวนั้นถูกสร้างขึ้นมาใหม่หรือว่าถูก Recreate ก็สามารถใช้วิธีแบบเดียวกับ Activity ได้เลย โดยเช็คว่าตัวแปร savedInstanceState
มีค่าเป็น null
หรือไม่
// HomeFragment.kt
class HomeFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
if (savedInstanceState == null) {
// Fragment เพิ่งถูกสร้างขึ้นมา
} else {
// Fragment ถูก Recreate
}
/* ... */
}
}
สมมติว่า Fragment ที่เจ้าของบล็อกสร้างขึ้น จำเป็นต้องคืนข้อมูลใน onCreate
เพื่อให้ทำงานต่อได้อย่างถูกต้อง
เจ้าของบล็อกจะต้องเขียนโค้ดสำหรับ State Handling ใน Fragment แบบนี้
// HomeFragment.kt
class HomeFragment: Fragment() {
private var name: String? = null
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("name", name)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
name = savedInstanceState.getString("name")
}
}
}
Fragment ที่ถูกทำลายระหว่างการทำงานของ Activity ได้
ถ้าเป็น Fragment ที่ถูกสร้างขึ้นมาพร้อม ๆ กับ Activity และทำงานควบคู่ไปด้วยตลอดเวลาจนจบ Lifecycle ของ Activity ก็อาจจะไม่ต้องทำอะไรเพิ่มเดิมนอกจากการทำ State Handling ตามที่ได้อธิบายไปในก่อนหน้านี้
แต่ถ้าเป็น Fragment ที่สามารถถูกทำลายชั่วคราวจากการที่ไม่ได้ใช้งานเป็นระยะเวลาหนึ่ง (เพื่อไม่ให้สิ้นเปลืองทรัพยากรเครื่อง) ไม่ว่าจะเป็น Fragment ที่อยู่ใน Fragment Back Stack หรือ Fragment ที่ Off-screen อยู่บน View Pager ใน ณ ตอนนั้น
สำหรับ Fragment ที่ใช้งานในลักษณะดังกล่าวจะมีปัญหาว่าไม่สามารถคืนข้อมูลใน onCreateView
ไม่ได้ในบางครั้ง
เพราะ Fragment ตัวนั้นไม่ได้แสดงอยู่บน Activity จึงไม่จำเป็นต้องสร้าง View ขึ้นมา ทำให้หลังจากเกิด State Changes จะเรียกแค่คำสั่ง onCreate
เท่านั้น ส่วน onCreateView
, onViewCreated
และ onViewCreated
จะไม่ถูกเรียก ซึ่งต่างจาก Fragment ที่แสดงผลอยู่บน Activity ในขณะนั้น
ดังนั้นถ้านักพัฒนาใช้คำสั่งคืนข้อมูลใน onCreateView
หรือ onViewCreated
จะทำให้ข้อมูลไม่ถูกคืน และถ้าเกิด State Changes อีกครั้งในระหว่างนี้ ก็จะทำให้คำสั่ง onSaveInstanceState
ไม่มีผล เพราะข้อมูลยังไม่ได้ถูกคืนค่ากลับมา
เป็นปัญหาที่เกิดขึ้นกับ Fragment ที่ไม่ได้แสดงผลบน Activity ณ ตอนนั้น และเกิด State Changes มากกว่า 1 ครั้งขึ้นไปในระหว่างนั้น
เพื่อแก้ปัญหาดังกล่าว นักพัฒนาจะต้องเพิ่มคำสั่งเข้าไปใน onCreate
แบบนี้ด้วย
// HomeFragment.kt
class HomeFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
retainInstance = true
}
/* ... */
}
การกำหนดค่า retainInstance
เป็น true
จะเป็นการบอกให้ระบบแอนดรอยด์เก็บ Instance ของ Fragment ในตอนที่เกิด State Changes ให้ด้วย เพื่อไม่ให้ Fragment ถูก Recreate ในตอนที่เกิด State Changes นั่นเอง
ไม่ต้องทำ State Handling ก็ได้ ถ้าไม่ส่งผลต่อการทำงานของ Fragment
เป้าหมายหลักของการทำ State Handling คือการเก็บข้อมูลจำพวก UI State ที่ถือไว้ใน Fragment ให้รอดพ้นจาก State Changes และทำให้ Fragment กลับมาทำงานต่อได้อย่างถูกต้อง
แต่ในกรณีที่ Fragment ตัวนั้นถูกออกแบบให้ไม่มีข้อมูลจำพวก UI State เก็บไว้เลย นักพัฒนาก็อาจจะไม่ต้องทำ State Handling ก็ได้
สรุป
Fragment เป็นอีกหนึ่ง Component ที่ได้รับผลกระทบจาก State Changes ไม่ต่างจาก Activity ทำให้นักพัฒนาต้องทำ State Handling สำหรับข้อมูลที่เก็บไว้ใน Fragment ด้วย โดยจะมีรูปแบบคำสั่งที่คล้ายกับ Activity แต่ก็ไมได้่เหมือนกันทั้งหมด เพราะทั้งคู่มี Lifecycle และรูปแบบในการทำงานที่แตกต่างกัน จึงทำให้คำสั่งที่ใช้ใน Fragment มีความหลากหลายมากกว่าเพื่อให้เอื้อกับการนำ Fragment ไปใช้งานในรูปแบบต่าง ๆ ที่ไม่สามารถทำใน Activity ได้