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

ViewBinding ในบทความนี้หมายถึง ViewBinding ซึ่งเป็นวิธีทำ View Binding แบบใหม่ที่ทางทีมพัฒนาแอนดรอยด์ได้เพิ่มเข้ามาใน Android Studio 3.6 ที่จะช่วยให้นักพัฒนาสามารถจัดการเรื่อง View Binding ได้สะดวกขึ้น

หมายเหตุ - ViewBinding จะหมายถึงวิธีการทำ View Binding แบบใหม่ สังเกตได้จากการเว้นวรรคตัวหนังสือ (ระวังสับสนระหว่าง ViewBinding และ View Binding ที่ใช้ในบทความนี้)

สำหรับเรื่องราวของ View Binding ในรูปแบบอื่นๆที่มีให้ใช้บนแอนดรอยด์นั้น สามารถอ่านได้ที่บทความ View Binding บนแอนดรอยด์ทำแบบไหนได้บ้างนะ?

View Binding บนแอนดรอยด์ทำแบบไหนได้บ้างนะ?
เจ้าของบล็อกเชื่อว่าคงไม่มีนักพัฒนาคนไหนที่ไม่รู้จักกับการทำ View Binding บนแอนดรอยด์ เพราะมันคือขั้นตอนพื้นฐานที่นักพัฒนาทุกคนต้องทำ เพื่อให้โค้ด Java หรือ Kotlin ในโปรเจคแอนดรอยด์สามารถเรียกใช้งาน View ที่อยู่ใน Layout XML

โดยข้อดีของ ViewBinding ที่เจ้าของบล็อกพูดถึงในบทความนี้ ก็คือไม่ต้องกลัวว่าจะกำหนด ID ของ View ผิดตัว เพราะว่า ViewBinding จะสร้างคลาส Binding ขึ้นมาให้นักพัฒนาสามารถเรียก View ที่มีทั้งหมดใน Layout นั้นๆได้ทันที

เปิดใช้งาน ViewBinding

การเริ่มใช้งาน ViewBinding จะต้องเปิดใช้งานใน build.gradle ก่อน โดยคำสั่งจะมี 2 แบบ โดยแยกตามเวอร์ชันของ Android Gradle Plugin

// Android Gradle Plugin 3.6
...
android {
    ...
    viewBinding {
        enabled = true
    }
}
// Android Gradle Plugin 4.0 or higher
...
android {
    ...
    buildFeatures {
        viewBinding = true
    }
}

หลังจาก Sync Project เสร็จก็จะสามารถใช้งาน ViewBinding ได้ทันที

โดย ViewBinding จะสร้างคลาสที่มีชื่อต่อท้ายว่า Binding สำหรับ Layout แต่ละตัวให้โดยอัตโนมัติ ยกตัวอย่างเช่น

  • activity_main.xml จะได้เป็น ActivityMainBinding
  • view_profile_item.xml จะได้เป็น ViewProfileItemBinding

คลาส Binding จะมี Public Method ให้ใช้งานอยู่ 3 Method ด้วยกันดังนี้

fun bind(view: View)

fun inflate(inflater: LayoutInflater)

fun inflat(
    inflater: LayoutInflater, 
    parent: ViewGroup?, 
    attachToParent: Boolean
)

Method ทั้ง 3 ตัวนี้จะทำหน้าที่เหมือนๆกัน และ Return ค่าออกมาเป็นคลาส Binding เหมือนกัน แต่จะมีเงื่อนไขในการเรียกใช้งานที่แตกต่างกัน

การเรียก View ต่างๆที่อยู่ในคลาสของ ViewBinding

สำหรับ View ต่างๆที่จะอยู่ในคลาส Binding นั้นจะมีเฉพาะ View ที่มีการกำหนด ID ไว้เท่านั้น โดยนักพัฒนาสามารถเรียก View เหล่านั้นได้ทันที โดยไม่ต้อง Cast Class ให้เสียเวลา

เมื่อมีการเพิ่ม View ใดๆเข้าไปใน Layout Resource เจ้าคลาส Binding ก็จะทำการอัปเดตให้ทันที ไม่ต้อง Build Project ใหม่ให้เสียเวลาอีกด้วย

ในกรณีที่คลาส Binding เกิด Error ขึ้น ไม่สามารถเรียกใช้งานได้ ก็ให้ลองเช็คดูว่าโค้ดในโปรเจคตรงจุดอื่นๆมีปัญหาหรือไม่ เพราะถ้า Build Project ไม่ผ่าน คลาส Binding ก็จะไม่ถูกสร้างขึ้นมาด้วยเช่นกัน

และในกรณีที่มีการใช้ <include> และต้องการเรียก View ที่อยู่ในนั้น จะต้องตั้ง View ID ให้กับ <include> เพื่อให้สามารถเรียก View ที่ต้องการผ่าน ViewBinding ได้

<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout ... >
    
    <!-- more -->

    <include
        android:id="@+id/layoutNavigationHeader"
        layout="@layout/layout_navigation_header" />

</androidx.constraintlayout.widget.ConstraintLayout>

โดยจะต้องเรียกเรียก View ID ของ <include> นั้นๆก่อน แล้วถึงจะเรียก View ที่อยู่ข้างในนั้นๆได้

การสร้าง ViewBinding ในรูปแบบต่างๆ

ใช้กับ Activity

ใน Activity จะมีคำสั่ง getLayoutInflater() อยู่แล้ว จึงทำให้นักพัฒนาสามารถสร้างคลาส Binding ขึ้นมาด้วยคำสั่ง inflate(inflater: LayoutInflater) ได้เลย

ยกตัวอย่างเช่น MainActivity.kt ที่มี Layout ชื่อ activity_main.xml

// MainActivity.kt
class MainActivity : AppCompatActivity() {
    private val binding: ActivityMainBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        ...
    }
}

สำหรับ Activity จะกำหนด Layout ผ่าน setContentView(...) ที่จะต้องกำหนดค่าเป็น Layout ID หรือ View เท่านั้น จึงสามารถใช้ binding.root เพื่อโยน Root View ของ Layout เข้าไปใน Method ตัวนี้ได้เลย

ใช้กับ Fragment

Fragment นั้นจะมีคำสั่ง getLayoutInflater() เช่นเดียวกับ Activity จึงใช้วิธีเดียวกันเพื่อสร้างคลาส Binding ขึ้นมา

ยกตัวอย่างเช่น ProfileFragment.kt ที่มี Layout ชื่อ fragment_profile.xml

// ProfileFragment.kt
class ProfileFragment : Fragment() {
    private val binding: FragmentProfileBinding by lazy {
        FragmentProfileBinding.inflate(layoutInflater)
    }

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

ต่างกับ Activity ก็ตรงที่จะต้องกำหนด Layout ให้กับ Fragment ผ่าน onCreateView(...) เท่านั้นเอง โดยจะต้อง Return ค่าใน Method นี้เป็น View ของ Layout ที่ต้องการ จึงสามารถใช้ binding.root ได้เลย

ใช้กับ ViewHolder ของ RecyclerView

เนื่องจากการสร้าง ViewHolder จะแตกต่างจาก Activity และ Fragment อยู่เล็กน้อย จึงมีคำสั่งอย่าง inflate(inflater: LayoutInflater, parent: ViewGroup?, attachToRoot: Boolean) ให้ใช้งานแทน

ยกตัวอย่างเช่น TimelineAdapter.kt กับ TimelineItemViewHolder.kt ที่มี Layout ชื่อ item_timeline.xml

// TimelineItemViewHolder.kt
class TimelineItemViewHolder(
    val binding: ItemTimelineBinding
) : RecyclerView.ViewHolder(binding.root) {
    ...
}
// TimelineAdapter.kt
class TimelineAdapter : RecyclerView.Adapter<TimelineItemViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TimelineItemViewHolder {
        val binding = ItemTimelineBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return TimelineItemViewHolder(binding)
    }
    ...
}

เนื่องจากคลาส Adapter หรือ ViewHolder ไม่ได้มี LayoutInflater ในตัวเองเหมือนกับ Activity หรือ Fragment จึงต้องสร้างขึ้นมาเองด้วย Context

และคลาส Binding ที่สร้างขึ้นมาก็จะส่งเข้าไปในคลาส TimelineItemViewHolder ด้วย

ใช้กับ Custom View

การใช้ ViewBinding กับ Custom View นั้นจะใช้ inflate(inflater: LayoutInflator) แล้วแล้วนำ binding.root ไปกำหนดลงใน Custom View ผ่านคำสั่ง addView(view: View)

ยกตัวอย่างเช่น GreetingView.kt ที่มี Layout ชื่อ view_greeting.xml

// GreetingView.kt
class GreetingView : FrameLayout {
    private val binding: ViewGreetingBinding by lazy {
        ViewGreetingBinding.inflate(LayoutInflater.from(context))
    }

    constructor(context: Context) : super(context) {
        setup()
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        setup()
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        setup()
    }

    private fun setup() {
        addView(binding.root)
        ...
    }
}

ใช้กับ View ที่มีอยู่แล้ว

ในกรณีที่เรียกใช้งาน ViewBinding ในคลาสบางตัวที่มี View ให้อยู่แล้ว ไม่ต้อง Inflate จาก Layout Resource เอง ก็สามารถนำมาทำเป็นคลาส Binding ได้เช่นกัน

ยกตัวอย่างเช่น PromotionCardView.kt ที่มี Layout ชื่อ view_promotion_card.xml ที่มีการ Inflate Layout ให้เรียบร้อยแล้ว

// PromotionCardView.kt
class PromotionCardView() {
    private lateinit var binding: ViewPromotionCardBinding
    ...
    private fun bindView(view: View) {
        binding = ViewPromotionCardBinding.bind(view)
    }
}

Known Issue

  • ViewBinding มีปัญหา NullPointerException เวลาใช้งานกับ <merge>

สรุป

ViewBinding ก็เป็นอีกวิธีหนึ่งที่จะช่วยให้นักพัฒนาจัดการเรื่อง View Binding ในโปรเจคได้ง่าย ไม่ต้องกลัวว่าจะเรียก View ID ผิดไฟล์ และชื่อที่กำหนดไว้ใน Layout Resource ก็จะถูกแปลงให้กลายเป็น Camel Case ให้ในทันที ทำให้ง่ายทั้งตอนที่สร้าง และตอนที่เรียกใช้งาน

แต่ทั้งนี้ทั้งนั้น การใช้ View Binding วิธีอื่นๆก็ยังสามารถใช้งานได้ปกติ ถ้าไม่มีปัญหาใดๆหรือมี Pain Point อะไรจากวิธีเดิมๆ ก็ไม่จำเป็นต้องเปลี่ยนมาใช้ ViewBinding ก็ได้ครับ