หลังจากที่เกริ่นไปคร่าว ๆ เกี่ยวกับ Fragment ในบทความตอนที่แล้ว คราวนี้มาดูวิธีการใช้งาน Fragment เบื้องต้นกันต่อดีกว่า

บทความในซีรีย์เดียวกัน

อย่างที่บอกไปก่อนหน้านี้ว่านักพัฒนาแอนดรอยด์จะใช้งาน Fragment ที่อยู่ใน androidx.fragment.app.Fragment ของ Library ที่ชื่อว่า AndroidX Fragment​

implementation "androidx.fragment:fragment:<latest_version>"

โดยปกติแล้ว AndroidX Fragment จะอยู่ใน AndroidX AppCompat ด้วย ซึ่งเป็น Library พื้นฐานที่จะได้พบเจอทุกครั้งเมื่อสร้างโปรเจคขึ้นมาใหม่

แต่เพื่อให้ใช้งาน Fragment ได้ง่ายขึ้น เจ้าของบล็อกขอแนะนำให้เพิ่ม Dependency ของ Android KTX สำหรับ Fragment เข้าไปด้วย

implementation "androidx.fragment:fragment-ktx:<latest_version>"

Library ดังกล่าวจะเป็น Extension สำหรับการใช้งาน Fragment ในภาษา Kotlin ซึ่งจะมี Extension ต่าง ๆ ที่ทำให้นักพัฒนาเขียนโค้ดได้ง่ายขึ้นและสั้นกว่าเดิม

หรือจะเพิ่มแค่ Android KTX ของ Fragment ก็ได้ เพราะข้างในนั้นก็จะมี AndroidX Fragment อยู่แล้วเช่นกัน

สร้าง Fragment

ในการสร้าง Fragment จะสร้างคลาส Fragment ขึ้นมาเองตั้งแต่แรกหรือใช้เครื่องมือใน Android Studio ก็ได้เช่นกัน

ในกณีที่ต้องการสร้างผ่านเครื่องมือของ Android Studio ก็สามารถคลิกขวาที่ Package Name ที่ต้องการสร้าง Fragment แล้วเลือก New > Fragment > Fragment (Blank) ได้เลย

ในระหว่างนี้จะเห็นว่าแถบเมนูของ Fragment จะมีให้เลือกสร้าง Fragment ในรูปแบบต่าง ๆ เพื่อช่วยลดระยะเวลาในการเขียนโค้ดที่ไม่จำเป็นให้น้อยลง แต่สำหรับการเรียนรู้เกี่ยวกับ Fragment ในเบื้องต้น เจ้าของบล็อกขอแนะนำให้เลือกเป็น Fragment (Blank) เพื่อให้มีแต่โค้ดที่จำเป็นต้องการเริ่มต้นเท่านั้น

Fragment ก็เป็น Component ตัวหนึ่งเหมือนกับ Activity ที่ใช้งานควบคู่กับ Layout Resource ในกรณีที่สร้างผ่าน Android Studio ก็จะสร้าง Layout Resource คู่กับ Fragment ให้โดยอัตโนมัติ แต่ถ้าสร้างไฟล์เองทั้งหมดก็อย่าลืมสร้าง Layout Resource ด้วยล่ะ

ถ้าตัดเรื่องของ UI ออกไป การสร้าง Fragment ในรูปแบบเริ่มต้นที่ง่ายต่อการทำความเข้าใจที่สุดจะเป็นแบบนี้

// HomeFragment.kt
class HomeFragment : Fragment() {
    private lateinit var binding: FragmentHomeBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = FragmentHomeBinding.inflate(inflater, container, false)
        return binding.root
    }

    companion object {
        fun newInstance() = HomeFragment()
    }
}

จากโค้ดตัวอย่างดังกล่าว มีสิ่งที่นักพัฒนาควรรู้อยู่ 5 เรื่องด้วยกัน

เรื่องที่ 1 - เจ้าของบล็อกสร้าง Fragment ชื่อว่า HomeFragment.kt และสร้าง Layout Resource ชื่อว่า fragment_home.xml เพื่อใช้งานร่วมกัน

เรื่องที่ 2 - เจ้าของบล็อกใช้ View Binding เพื่อช่วยให้เรียกใช้งาน View ได้สะดวก จึงมี FragmentHomeBinding เพื่อทำ Layout Inflation สำหรับ View Binding เพิ่มเข้ามาด้วย (แนะนำ)

วิธีการใช้งาน ViewBinding
มาดูกันว่าการใช้งาน ViewBinding ในแต่ละแบบ มีวิธีเรียกใช้งานยังไงบ้าง

เรื่องที่ 3 - ถ้าสร้าง Fragment ผ่าน Android Studio โดยเลือก Fragment (Blank) จะพบว่ามีโค้ดอื่น ๆ ที่ถูกเตรียมไว้ให้บางส่วน ทำให้โค้ดดูเยอะกว่าโค้ดในตัวอย่างข้างบน

เรื่องที่ 4 - Fragment จะมี Lifecycle เช่นเดียวกับ Activity แต่มี Event เยอะกว่าและลำดับในการทำงานที่ยิบย่อยมากกว่า และหนึ่งในนั้นคือ onCreateView นั่นเอง ซึ่งจะพูดถึงอีกทีในบทความ Let’s Fragment — Lifecycle ของ Fragment

แก้ Link ของบทความนี้ด้วย

เรื่องที่ 5 - การทำ Fragment ไปใช้งานจะนิยมสร้างเป็น Method ไว้ใน Companion Object ว่า newInstance() แล้วให้เรียกใช้งานผ่าน Method ดังกล่าวแทน ซึ่งเป็นหนึ่งใน Best Practice ที่ควรทำ และจะมีพูดถึงอีกทีในบทความ Fragment ตอนที่ 3 - Fragment Creation

Fragment ตอนที่ 3 - Fragment Creation
ในบทความนี้จะมาพูดถึงการสร้าง Instance สำหรับ Fragment เพราะถึงแม้ว่า Fragment จะเป็นแค่คลาสตัวหนึ่งเหมือนกับคลาสทั่ว ๆ ไป แต่ก็มีเงื่อนไขที่สำคัญอยู่บ้างเหมือนกันนะ

ไม่ต้องประกาศ Fragment ไว้ใน Android Manifest หรอ?

ใช่ครับ นักพัฒนาไม่จำเป็นต้องประกาศ Fragment ไว้ใน Android Manifest แต่อย่างใด

นั่นก็เพราะว่า Fragment ไม่ใช่ App Component อย่าง Activity ที่สามารถถูกเรียกใช้งานจากระบบแอนดรอยด์หรือแอปภายนอกได้ โดย Fragment จะถูกเรียกใช้งานจาก Activity หรือ Fragment ที่อยู่ในแอปเดียวกันเท่านั้น หรือก็คือแอปภายนอกจะเรียกใช้งานโดยตรงไม่ได้นั่นเอง

การใช้งาน Fragment ในรูปแบบต่าง ๆ

อย่างที่เคยบอกไปก่อนหน้านี้ว่า Fragment ไม่สามารถทำงานได้ด้วยตัวเอง ต้องทำงานอยู่บน Activity เสมอ ดังนั้นการนำ Fragment ใด ๆ ไปใช้งานจึงขาด Activity ไปไม่ได้เลย

ในขณะเดียวกัน Fragment ก็สามารถอยู่บน Fragment ได้เช่นกัน แต่ Fragment ที่เป็น Root Parent ก็จะต้องอยู่บน Activity อยู่ดี

และนอกจากนี้ Fragment สามารถอยู่บน Activity หรือ Fragment มากกว่าหนึ่งตัวก็ได้ ไม่จำเป็นต้องมีแค่ตัวเดียวเสมอไป (ขึ้นอยู่กับการออกแบบของนักพัฒนาเลย)

รู้จักกับ Fragment Manager

ในการจัดการกับ Fragment เช่น เพิ่ม Fragment เข้าไปใน FragmentContainerView หรือสั่งให้ Pop Back Stack ของ Fragment (หลักการเดียวกับ Back Stack ของ Activity) จะต้องทำผ่าน Fragment Manager เท่านั้น

และในปัจจุบันนักพัฒนาสามารถใช้ Jetpack Navigation เพื่อช่วยจัดการกับ Fragment แทน Fragment Manager ได้เช่นกัน (แต่ในบทความนี้จะยังไม่พูดถึง)

การเพิ่ม Fragment ให้กับ Activity หรือ Fragment ด้วยกัน

ในการเพิ่ม Fragment เข้าไปใน Activity หรือ Fragment จะมีอยู่ 2 วิธีด้วยกัน

  • กำหนดไว้ใน Layout Resource ด้วย <FragmentContainerView> โดยตรง
  • กำหนดผ่าน Fragment Manager ควบคู่กับ <FragmentContainerView>
บทความนี้จะยังไม่พูดถึงการใช้ Fragment กับ Jetpack Navigation

กำหนดไว้ใน Layout Resource ด้วย <FragmentContainerView> โดยตรง

นักพัฒนาสามารถเพิ่ม <FragmentContainerView> ไว้ใน Layout Resource และกำหนด Fragment ที่ต้องการแบบนี้ได้เลย

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/homeFragment"
    android:name="com.akexorcist.fragment.basic.HomeFragment"
    android:layout_width="..."
    android:layout_height="..."
    tools:layout="@layout/fragment_home" />

โดยวิธีนี้จะมีสิ่งสำคัญอยู่ 3 อย่างด้วยกัน

  • android:id - ต้องกำหนด View ID ด้วยเสมอ
  • android:name - กำหนดเป็นชื่อคลาสของ Fragment ที่ต้องการ
  • tools:layout - เนื่องจาก Layout Preview ดึง UI ของ Fragment มาแสดงโดยตรงไม่ได้ จึงต้องกำหนด Layout Resource ของ Fragment ตัวนั้นเพื่อให้แสดงใน Layout Preview ได้

ใช้ Fragment Manager คู่กับ <FragmentContainerView>

แทนที่จะกำหนด Fragment ลงใน FragmentContainerView โดยตรง ก็เปลี่ยนมากำหนดผ่าน Fragment Manager แทน ดังนั้นการสร้าง FragmentContainerView ในวิธีนี้จึงไม่จำเป็นต้องกำหนดคลาส Fragment หรือ UI สำหรับ Layout Preview

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/fragmentContainer"
    android:layout_width="..."
    android:layout_height="..." />

เพราะ Fragment ที่จะเพิ่มลงใน FragmentContainerView จะกำหนดผ่าน Fragment Manager แทน โดยที่ Fragment Manager จะมี 2 แบบเช่นเดียวกับ Fragment และตัวที่จะใช้ก็คือ androidx.fragment.app.FragmentManager ที่เป็น Fragment Manager ของ AndroidX Fragment นั่นเอง

แต่ในความเป็นจริง นักพัฒนาไม่ต้องสร้าง Fragment Manager ขึ้นมาเอง เพราะไม่ว่าจะเป็น Activity หรือ Fragment ก็ตาม จะมี Fragment Manager ให้เรียกใช้งานเสมอ

// Activity
val activity: Activity = /* ... */
val fragmentManager: FragmentManager = activity.supportFragmentManager

// Fragment
val fragment: Fragment = /* ... */
val fragmentManager: FragmentManager = fragment.childFragmentManager
val fragmentManager: FragmentManager = fragment.parentFragmentManager
childFragmentManager กับ parentFragmentManager จะมีจุดประสงค์ในการใช้งานต่างกัน ดังนั้น ณ ตอนนี้ให้ใช้เป็น childFragmentManager ไปก่อน

และการเพิ่ม Fragment ด้วย Fragment Manager จะใช้คำสั่งแบบนี้

val fragmentManager: FragmentManager = /* ... */
fragmentManager.commit {
    replace(R.id.fragmentContainer, HomeFragment.newInstance())
    addToBackStack(null)
}

ถ้าใช้ View Binding แล้วอยากจะกำหนด View ID แบบนี้แทน ก็ทำได้เช่นกัน

val binding: HomeFragmentBinding = /* ... */
val fragmentManager: FragmentManager = /* ... */
fragmentManager.commit {
    replace(binding.fragmentContainer.id, HomeFragment.newInstance())
    addToBackStack(null)
}

สำหรับวิธีนี้จะมีสิ่งสำคัญอยู่ 3 อย่างด้วยกัน

  • commit { ... } - เป็น Extension Function ของ Android KTX สำหรับ Fragment แทนการพิมพ์คำสั่งที่ยาวกว่านี้ (ใช้เถอะ)
  • replace - กำหนดให้ Fragment Manager เพิ่ม Fragment แทนที่ของเดิมที่อยู่ใน FragmentContainerView (ถ้าไม่มี Fragment ตั้งแต่แรก ก็จะทำให้ Fragment ตัวที่เราเพิ่มเข้าไปเป็นตัวแรกสุด)
เราสามารถเพิ่ม Fragment ไว้ใน View Group ใด ๆ ก็ได้ แต่การใช้ FragmentContainerView เป็น Best Practice ที่ทีมแอนดรอยด์แนะนำ
  • addToBackStack - กำหนดให้เพิ่ม Fragment เข้าไปใน Back Stack ของ Fragment Manager (ถ้าไม่ต้องการให้อยู่ใน Back Stack ก็ไม่ต้องใส่)
สามารถดูรายละเอียดเกี่ยวกับ Back Stack ของ Fragment ได้ในบทความ Task และ Back Stack ตอนที่ 2 - Back Stack

ใช้วิธีไหนดีกว่ากัน?

ในการเพิ่ม Fragment ลงใน FragmentContainerView จะใช้วิธีไหนก็ได้ โดยขึ้นอยู่กับความต้องการของนักพัฒนาว่าอยากจะให้ Fragment ทำงานในลักษณะแบบไหน

การกำหนด Fragment ไว้ใน FragmentContainerView โดยตรงจะเหมาะกับกรณีที่นักพัฒนารู้อยู่แล้วว่า Fragment ตัวไหนจะเป็นตัวแรกสุดในนั้น

ส่วนการใช้ Fragment Manager จะมีข้อดีคือสามารถเพิ่ม Fragment ตัวใหม่ ๆ เข้าไปใน FragmentContainerView ได้เรื่อย ๆ (จึงมีคำสั่ง addToBackStack สำหรับกำหนด Back Stack ของ Fragment ใน Fragment Manager) โดยการใช้คำสั่งอย่าง replace ก็เป็นแค่หนึ่งในการใช้งานโดยทั่วไปเท่านั้น ส่วนคำสั่งอื่นที่เกี่ยวข้องจะพูดถึงในภายหลัง

สรุป

เพื่อพัฒนาแอปที่มีรูปแบบการทำงานที่ซับซ้อนได้ง่าย การใช้ Fragment จึงเป็นเรื่องสำคัญ ถึงแม้ว่า Fragment จะมีการทำงานที่ซับซ้อนมากกว่า Activity ก็ตาม แต่การสร้าง Fragment เพื่อใช้งานในเบื้องต้นก็ไม่ได้ซับซ้อนแต่อย่างใด

นอกจากนี้ก็ยังมีเรื่องอื่น ๆ ของ Fragment ที่นักพัฒนาควรรู้เพิ่มเติมเพื่อใช้งาน Fragment ได้อย่างมีประสิทธิภาพมากขึ้น