Fragment ก็เป็นหนึ่งใน Component ที่มี Lifecycle และได้รับผลกระทบจาก State Changes ไม่ต่างจาก Activity ทำให้นักพัฒนาจำเป็นต้องจัดการกับข้อมูลจำพวก UI State เพื่อให้ Fragment ทำงานต่อได้อย่างเหมาะสม

ในปัจจุบันจะไม่นิยมเก็บข้อมูลจำพวก UI State ไว้ใน Fragment โดยตรง ควรเก็บไว้ใน ViewModel ตามรูปแบบของ App Architecture ที่ทีมแอนดรอยด์แนะนำ

บทความในชุดเดียวกัน

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 ได้