หลังจากที่ใช้งาน Fragment กันได้แล้ว คราวนี้ก็จะมาถึงคำถามยอดนิยมสำหรับผู้ที่หลงเข้ามาอ่านที่ใช้งาน Fragment นั่นก็คือการรับส่งข้อมูลของ Fragment ไม่ว่าจะเป็นระหว่าง Activity ←→ Fragment หรือ Fragment ←→ Fragment ก็ตาม
บทความในซีรีย์เดียวกัน
- มารู้จักกับ Fragment กันเถอะ~
- เริ่มต้นง่ายๆกับ Fragment แบบพื้นฐาน
- ว่าด้วยเรื่องการสร้าง Fragment จาก Constructor ที่ถูกต้อง
- รู้จักกับ FragmentTransaction สำหรับการแสดง Fragment [ตอนที่ 1]
- รู้จักกับ FragmentTransaction สำหรับการแสดง Fragment [ตอนที่ 2]
- Lifecycle ของ Fragment (Fragment Lifecycle)
- วิธีการรับส่งข้อมูลของ Fragment
- มาทำ View Pager กันเถิดพี่น้อง~ [ตอนที่ 1]
- มาทำ View Pager กันเถิดพี่น้อง~ [ตอนที่ 2]
- เพิ่มลูกเล่นให้กับ View Pager ด้วย Page Transformer
โดยปกติแล้วการทำงานของแอปที่มีความซับซ้อน เป็นเรื่องปกติที่จะต้องรับส่งข้อมูลระหว่าง Component กันอยู่บ่อยๆ แต่วิธีที่สามารถทำได้ก็มีมากมายเหลือเกิน ดังนั้นนักพัฒนาควรต้องระมัดระวังในการเลือกใช้แต่ละวิธี เพื่อไม่ทำให้วิธีเหล่านั้นทำให้โครงสร้างในการทำงานของแอปต้องเละเทะยิ่งกว่าเดิม
สำหรับการรับส่งข้อมูลของ Fragment นั้นทำได้หลากหลายวิธี ขึ้นอยู่กับความเหมาะสมและรูปแบบในการทำงานของ Fragment
- ส่งข้อมูลด้วย Constructor Arguments (Activity → Fragment)
- ส่งข้อมูลผ่าน Event Listener (Fragment → Activity)
- ส่งข้อมูลด้วย Direct Method Call (Activity → Fragment)
- ส่งข้อมูลด้วย Event Bus (Activity/Fragment ↔ Fragment)
- ส่งข้อมูลด้วย ViewModel (Activity/Fragment ↔ Fragment) - แนะนำวิธีนี้
- ส่งข้อมูลด้วย Fragment Result API (Activity/Fragment ↔ Fragment ) - แนะนำวิธีนี้
โดยแต่ละแบบจะมีรูปแบบในการใช้งานดังนี้
ส่งข้อมูลด้วย Constructor Arguments
Activity → Fragment
ชื่อหัวข้ออาจจะไม่คุ้นหู แต่เป็นวิธีที่ใช้กันบ่อย ซึ่งเป็นการกำหนดให้ Activity ส่งข้อมูลเป็นแบบ Bundle ไปให้ Fragment ตั้งแต่ตอนสร้าง Fragment ขึ้นมานั่นเอง
// Fragment
class AwesomeFragment : Fragment() {
private var message: String? = null
private var number = 0
companion object {
const val EXTRA_MESSAGE = "extra_message"
const val EXTRA_NUMBER = "extra_number"
fun newInstance(message: String?, number: Int): AwesomeFragment {
return AwesomeFragment().apply {
arguments = Bundle().apply {
putString(EXTRA_MESSAGE, message)
putInt(EXTRA_NUMBER, number)
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let { bundle: Bundle ->
message = bundle.getString(EXTRA_MESSAGE)
number = bundle.getInt(EXTRA_NUMBER)
}
}
/* ... */
}
และ Activity ก็สามารถแนบข้อมูลที่ต้องการผ่านคำสั่ง newInstance(...)
ที่เตรียมไว้ใน Fragment
// Activity
class AwesomeActivity : AppCompatActivity() {
/* ... */
private fun openAwesomeFragment() {
supportFragmentManager.commit {
val fragment: AwesomeFragment = AwesomeFragment.newInstance(
message = "Secret Password",
number = 1234
)
replace(R.id.layoutFragmentContainer, fragment)
}
}
}
คำสั่งcommit { }
ที่เจ้าของบล็อกใช้เป็น Extension Function ของ KTX Fragment เพื่อช่วยให้คำสั่งกระชับมากขึ้น ส่วนR.id.layoutFragmentContainer
เป็น ViewGroup เพื่อใช้เป็นพื้นที่ในการแสดง Fragment ท่ีได้สร้างขึ้นมา
เมื่อ Fragment ถูกสร้างขึ้นมาและเริ่มทำงาน ก็จะดึงข้อมูลจาก arguments
เพื่อนำไปใช้งานนั่นเอง
โดยวิธีนี้มีไว้ใช้ในกรณีที่ Activity ต้องการส่งข้อมูลให้ Fragment แค่ตอนที่ถูกสร้างขึ้นเท่านั้น แต่ถ้าเป็น Fragment ที่กำลังทำงานอยู่ (ถูกสร้างขึ้นมาเรียบร้อยแล้ว) จะไม่สามารถใช้วิธีแบบนี้ได้
ส่งข้อมูลผ่าน Event Listener
Fragment → Activity
เป็นการสร้าง Event Listener เพื่อให้ Fragment เรียกใช้งาน เวลาที่ต้องการส่งค่าระหว่างกัน ยกตัวอย่างเช่น อยากจะให้ Fragment ส่งข้อมูลไปหา Activity ได้ จึงสร้าง Event Listener ขึ้นมาแบบนี้
// AwesomeFragmentListener.kt
interface AwesomeFragmentListener {
fun onButtonOkClick()
fun onButtonCloseClick()
fun onLoginSuccess(data: UserData?)
}
และที่ฝั่ง Activity ก็ให้ประกาศ AwesomeFragment.Listener
ในรูปแบบของการ Implement เตรียมไว้แบบนี้
// Activity
class AwesomeActivity : AppCompatActivity(), AwesomeFragmentListener {
/* ... */
override fun onOkClick() { }
override fun onCloseClick() { }
override fun onLoginSuccess(data: UserData?) { }
}
และเวลาที่ Fragment ต้องการส่งข้อมูลไปให้ Activity ก็ให้เรียกผ่าน Event Listener ตัวนั้นแทน
// Fragment
class AwesomeFragment : Fragment() {
/* ... */
private fun sendOkButtonClickEvent() {
(activity as? AwesomeFragmentListener)?.onOkClick()
}
}
ในกรณีที่ Activity ไม่ได้ประกาศไว้ตั้งแต่แรก คำสั่งดังกล่าวก็จะไม่มีผลอะไร
จะเห็นว่าวิธีนี้จะช่วยให้นักพัฒนานำ Fragment ไปใช้งานกับ Activity ใดๆก็ได้ ไม่ต้องกังวลว่า Fragment จะผูกอยู่กับ Activity ตัวใดตัวหนึ่ง
ในทางกลับกัน ถ้าอยากจะส่งข้อมูลจาก Activity ไปให้ Fragment ก็สามารถใช้วิธีนี้ได้เช่นกัน (แต่ไม่ค่อยนิยมกันซักเท่าไร)
ส่งข้อมูลด้วย Direct Method Call
Activity → Fragment
เนื่องจาก Activity ทำหน้าที่สร้าง Fragment อยู่แล้ว จึงสามารถเก็บ Fragment ไว้เพื่อส่งข้อมูลในช่วงเวลาที่ต้องการได้เลย
// Activity
class AwesomeActivity: AppCompatActivity() {
private lateinit val fragment: AwesomeFragment
/* ... */
private fun initAwesomeFragment() {
supportFragmentManager.commit {
val fragment: AwesomeFragment = AwesomeFragment.newInstance(
message = "Secret Password",
number = 1234
)
replace(R.id.layoutFragmentContainer, fragment)
}
}
fun onInfoSaveClick() {
/* ... */
fragment.onUpdateData(/* ... */)
}
}
// Fragment
class AwesomeFragment: Fragment() {
/* ... */
fun onUpdateData(/* ... */) {
/* ... */
}
}
แต่ไม่แนะนำให้ Fragment ไปเรียก Activity ด้วยคำสั่งแบบนี้ เพราะจะเป็นการผูก Fragment เข้ากับ Activity ตัวนั้นๆโดยตรง ไม่สามารถนำ Fragment ไปใช้ซ้ำกับ Activity อื่นๆได้เลย
ส่งข้อมูลด้วย Event Bus
Activity / Fragment ↔ Fragment
สำหรับวิธีนี้เป็นการใช้ Event Bus เข้ามาช่วย ซึ่งจะใช้ Library ตัวไหนก็ได้ เพราะใช้ Concept เดียวกัน แต่สำหรับตัวอย่างนี้จะใช้ Otto
ก่อนที่จะเริ่มส่งข้อมูลผ่าน Event Bus จะต้อง Register และ Unregister ให้กับ Activity หรือ Fragment ปลายทางที่จะรับข้อมูลผ่าน Event Bus เสียก่อน
// Activity
class AwesomeActivity : AppCompatActivity() {
/* ... */
override fun onStart() {
/* ... */
Bus.getInstance().register(this)
}
override fun onStop() {
/* ... */
Bus.getInstance().unregister(this)
}
}
โดยจะใส่ไว้ใน onStart()
และ onStop()
นั่นเอง
จากนั้นก็เตรียม Method สำหรับรับข้อมูลจาก Event Bus โดยใช้ @Subscribe
ซึ่งเป็น Annotation ของ Otto
// Activity
class AwesomeActivity : AppCompatActivity() {
/* ... */
@Subscribe
fun onUserInfoUpdate(info: UserInfo) {
// Do something when any event on fragment was happened
}
}
และเมื่อ Fragment อยากส่งข้อมูลมาให้ Activity ตัวนี้ก็เพียงแค่ใช้คำสั่งแบบนี้
val info: UserInfo = /* ... */
Bus.getInstance().post(info)
ด้วย Concept ของ Event Bus จึงทำให้นักพัฒนาสามารถใช้ส่งข้อมูลระหว่าง Activity กับ Fragment ได้ตามใจชอบ หรือจะส่งระหว่าง Fragment กับ Fragment ที่อยู่ใน Activity ตัวเดียวกันก็ได้เช่นกัน
ในขณะเดียวกัน จุดที่นักพัฒนาต้องระวังในการใช้งาน Event Bus ก็คือความอิสระมากเกินไปของ Event Bus ที่สามารถส่งข้อมูลจากที่ไหน ไปที่ไหนก็ได้ ซึ่งถ้าใช้เยอะจนเกินไปก็อาจจะทำให้การรับส่งข้อมูลภายในแอปสามารถดูหรือตรวจสอบเวลาเกิดปัญหาได้ยาก
ส่งข้อมูลผ่าน ViewModel ด้วย LiveData (แนะนำ)
Activity / Fragment ↔ Fragment
ViewModel และ LiveData เป็น Component ตัวหนึ่งใน Android Architecture Components ที่ทีมพัฒนาของ Android ที่ Google สร้างขึ้นมาเพื่อช่วยให้นักพัฒนาสามารถจัดการกับทำงานของแอปได้ง่ายขึ้น ซึ่งสามารถนำมาใช้งานเพื่อรับส่งข้อมูลระหว่าง Activity และ Fragment ได้อีกด้วย
โดยรายละเอียดของวิธีนี้เจ้าของบล็อกได้เขียนเป็นบทความแยกไว้แล้วใน ส่งข้อมูลระหว่าง Activity/Fragment แบบหล่อๆด้วย LiveData และ ViewModel ของ Android Architecture Components
ถ้าเป็นไปได้ก็แนะนำให้ใช้วิธีนี้ เพราะเป็นวิธีที่มีประสิทธิภาพ ไม่ทำลายโครงสร้างโค้ด และใช้งานได้ง่าย เหมาะกับการทำงานที่มีความซับซ้อน
ส่งข้อมูลด้วย Fragment Result API
Activity / Fragment ↔ Fragment
Fragment Result API เป็นอีกหนึ่งทางเลือกสำหรับการส่งข้อมูลระหว่าง Component โดยเน้นรูปแบบการใช้งานที่เรียบง่าย เหมาะกับการทำงานที่ไม่ซับซ้อนมากนัก
โดยรายละเอียดของวิธีนี้เจ้าของบล็อกได้เขียนเป็นบทความแยกไว้แล้วใน ส่งข้อมูลระหว่าง Activity/Fragment แบบง่าย ๆ ด้วย Fragment Result API
ถ้าเป็นไปได้ก็แนะนำให้ใช้วิธีนี้ เพราะเป็นวิธีที่ใช้งานง่าย มีประสิทธิภาพ ไม่ส่งผลกระทบกับโครงสร้างโค้ด และเหมาะกับการทำงานที่ไม่ซับซ้อนมากนัก
ส่งข้อมูลระหว่าง Fragment กับ Fragment
ที่ผ่านมาจะเน้นไปที่การรับส่งข้อมูลระหว่าง Activity กับ Fragment แต่ว่าในการทำงานจริงนั้น ไม่น้อยเลยที่นักพัฒนาจะต้องรับส่งข้อมูลระหว่าง Fragment กับ Fragment ที่อยู่ใน Activity ตัวเดียวกัน
ซึ่งกรณีแบบนี้จะทำได้หลายวิธี
สำหรับ Fragment ที่อยู่ใน Activity เดียวกัน
- การใช้ Event Bus เพื่อส่งข้อมูลระหว่าง Fragment กับ Fragment โดยตรงเลย
- Fragment ส่งข้อมูลให้ Activity ผ่าน Event Listener แล้วให้ Activity ส่งข้อมูลให้ Fragment ที่ต้องการด้วยการเรียก Method ของ Fragment นั้นๆโดยตรง
- ใช้ ViewModel ร่วมกัน
- ใช้ Fragment Result API
สำหรับ Fragment ที่อยู่ใน ViewPager เดียวกัน
- ใช้ PagerAdapter เป็นตัวกลางเพื่อส่งข้อมูลจาก Fragment หนึ่งไปอีก Fragment หนึ่ง
- ใช้ Event Bus ส่งข้ามหากันโดยตรง
- ใช้ ViewModel ร่วมกัน
- ใช้ Fragment Result API
สรุป
จะเห็นว่ารูปแบบในการรับส่งข้อมูลระหว่าง Activity กับ Fragment หรือ Fragment กับ Fragment นั้นมีหลายวิธีมาก ขึ้นอยู่กับทิศทางของข้อมูล จุดประสงค์ และช่วงเวลาในการทำงานของโค้ด
ถึงแม้ว่าเจ้าของบล็อกจะอธิบายรูปแบบต่างๆมากมาย แต่ส่วนหนึ่งก็เพื่อให้นักพัฒนาสามารถเข้าใจวิธีการใช้งานในรูปแบบต่างๆมากขึ้นเท่านั้น เพราะท้ายที่สุดแล้วก็อยากให้นักพัฒนาใช้ ViewModel + LiveData มากกว่า เพราะเป็นวิธีที่ค่อนข้างสะดวกและตอบโจทย์นักพัฒนามากที่สุดในตอนนี้