Data binding เป็นเทคนิคที่ใช้เมื่อคุณต้องการ "ยึด" ชิ้นส่วนข้อมูล (data ของคุณ) เข้ากับองค์ประกอบ UI ที่รับข้อมูลจากผู้ใช้ ในกระบวนการนี้ เมื่อ input ถูกอัปเดต ข้อมูลเบื้องหลังก็จะอัปเดตตามไปด้วย
สิ่งนี้ไม่ใช่แนวคิดใหม่ มีเฟรมเวิร์กมากมายที่นำมาใช้ในการออกแบบ (เช่น AngularJS/React/Vue)
บทความนี้ไม่เน้นเฟรมเวิร์ก front-end แต่เน้นการพัฒนาบนมือถือ Google ได้นำเสนอ Data Binding Library ใน Android ซึ่งเป็นส่วนหนึ่งของ Android Jetpack
หากคุณไม่คุ้นเคยกับ Jetpack อาจเป็นเพราะ Google ประกาศหยุดพัฒนา support libraries แทนด้วยการรองรับ AndroidX libraries (เวอร์ชันใหม่ของ support libraries)
ผมรู้ว่ามีบทความมากมายอธิบาย data binding ผ่าน adapter แต่นี้จะไม่เน้นนั้น แทนที่จะเป็นการใช้งานแบบ minimalist ที่ช่วยประหยัดเวลาโดยลดโค้ดให้น้อยที่สุด
ทำไมต้องใช้ Data Binding?
ถ้ายังไม่เชื่อ ให้อธิบายประโยชน์ผ่านตัวอย่าง สมมติคุณมีเมนู 3 ปุ่มแบบกำหนดเอง แต่ละปุ่มเป็น layout ตัวเอง

วิธีหนึ่งคือสร้าง XML layout 4 ไฟล์: 1 สำหรับ layout หลัก และ 3 สำหรับแต่ละปุ่ม
คุณต้องทำเช่นนี้เพราะแต่ละปุ่มนำทางไปส่วนต่างของแอป จึงต้องการข้อความและรูปภาพที่ต่างกัน
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="https://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_gravity="center_horizontal" android:layout_height="wrap_content" android:layout_width="wrap_content"> <ImageView android:id="@+id/imageView" android:layout_height="100dp" android:layout_width="100dp" android:src="@drawable/image_name" android:adjustViewBounds="true" android:scaleType="centerInside" /> <TextView android:id="@+id/textView" android:gravity="center_horizontal" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="Image Text" android:textSize="16sp" /> </LinearLayout>แม้โค้ดซ้ำไม่มาก (แค่ 3 layouts) แต่คิดดูแล้ว ค่อนข้างเสียเวลาจัดการโค้ดทั้งหมด ถ้านึกถึงแอปที่มี layout ซับซ้อนกว่านี้ เช่นแสดงสินค้าและรูปภาพ อาจนำไปสู่การทำซ้ำโค้ดที่น่าเบื่อมาก
ด้วย data binding เราสร้าง XML layout เพียง 1 ไฟล์ใช้กับปุ่มทั้งหมดได้
เริ่มต้นที่ไหน?
เราต้องให้โปรเจกต์เปิดใช้งาน data binding โดยเพิ่ม element dataBinding ใน build.gradle ของแอป:
android { compileSdkVersion 29 buildToolsVersion "29.0.2" defaultConfig { applicationId "com.tomerpacific.example" minSdkVersion 15 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" dataBinding { //<------- enabled = true } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } }หลัง sync โปรเจกต์ เราจะสร้าง data class ที่จะใช้ bind กับ layout
package com.tomerpacific.example import android.graphics.drawable.Drawable data class ButtonData(val buttonText: String, val buttonImageSrc : Drawable) สังเกตว่า class ButtonData มี 2 ฟิลด์:
buttonText— ข้อความที่จะแสดงใต้รูปภาพbuttonImageSrc— รับผิดชอบรูปภาพของปุ่ม
หากต้องการข้อมูลมากขึ้น ให้เพิ่มฟิลด์ใน data class ได้เลย
การ Binding จริง
ต่อไป ต้องประกาศ property variable ใน layout เพื่อให้ใช้งานได้ variable นี้จะ bind เข้ากับ data class ที่สร้าง ต้องทำ 2 อย่าง:
- ห่อ root layout element ด้วย tag layout
- เพิ่ม tag data ที่ประกอบ variable (
buttonData)
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="https://schemas.android.com/apk/res/android"> // <---- 1 <data> <variable name="buttonData" type="com.tomerpacific.example.ButtonData"/> // <---- 2 </data> <androidx.constraintlayout.widget.ConstraintLayout xmlns:app="https://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Example" android:textSize="30dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/linearLayout3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView2"> <ImageButton android:layout_width="100dp" android:layout_height="100dp" android:adjustViewBounds="true" android:scaleType="centerInside" android:src="@drawable/android"> </ImageButton> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="Image Text" android:textSize="16sp" /> </LinearLayout> <LinearLayout android:id="@+id/linearLayout3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView2" app:layout_constraintVertical_bias="0.504"> <ImageButton android:layout_width="100dp" android:layout_height="100dp" android:adjustViewBounds="true" android:scaleType="centerInside" android:src="@drawable/android_p_logo"> </ImageButton> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="Image Text" android:textSize="16sp" /> </LinearLayout> <LinearLayout android:id="@+id/linearLayout2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.200" app:layout_constraintStart_toEndOf="@+id/linearLayout3" app:layout_constraintTop_toBottomOf="@+id/textView2" app:layout_constraintVertical_bias="0.504"> <ImageButton android:layout_width="100dp" android:layout_height="100dp" android:adjustViewBounds="true" android:scaleType="centerInside" android:src="@drawable/android_studio_icon"> </ImageButton> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="Image Text" android:textSize="16sp" /> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout> </layout>สังเกตว่าเราสามารถลบ schema จาก main layout เดิมได้ เพราะย้ายไปที่ root layout tag แล้ว variable ที่เพิ่มมานั้นเชื่อมต่อกับ data class โดยตรง
ในไฟล์ MainActivity ต้องเพิ่มโค้ดจัดการ binding:
package com.tomerpacific.example import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.databinding.DataBindingUtil import com.tomerpacific.example.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main) binding.buttonData = ButtonData("First", resources.getDrawable(R.drawable.android)) } }ทุกครั้งที่สร้าง variable ภายใน layout จะมี binding class สร้างขึ้นอัตโนมัติ สำหรับ layout ชื่อ activity_main binding class จะชื่อ ActivityMainBinding
ข้อตกลงคือชื่อ layout + Binding ต่อท้าย
เนื่องจากเราประกาศ buttonData เป็น variable ใน layout มันจะถูกผนวกกับ binding object เราจึง assign instance ใหม่ของ ButtonData ได้
หลังทำทั้งหมด เราสามารถใช้ข้อมูลที่ bind ไว้ใน layout ได้
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/linearLayout3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView2"> <ImageButton android:layout_width="100dp" android:layout_height="100dp" android:adjustViewBounds="true" android:scaleType="centerInside" android:src="@{buttonData.buttonImageSrc}"> // <---- </ImageButton> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="@{buttonData.buttonText}" // <---- android:textSize="16sp" />และผลลัพธ์คือ:

รอสักครู่…
เรามี 3 ปุ่ม แต่ data class ใช้ได้แค่ 1 ปุ่ม แล้วจะผ่านได้อย่างไร?
package com.tomerpacific.example data class ButtonsData(val buttonsData : List<ButtonData>) { fun get(index: Int) : ButtonData { return buttonsData.get(index) } }เราต้อง override method get เพื่อให้รู้จักเมื่อใช้ใน layout
จากนั้น เปลี่ยน reference ใน activity_main.xml:
<data> <variable name="buttonsData" type="com.tomerpacific.example.ButtonsData"/> </data>และสร้าง binding ใหม่กับ data class ใหม่:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main) val firstButton : ButtonData = ButtonData("First", resources.getDrawable(R.drawable.android)) val secondButton : ButtonData = ButtonData("Second", resources.getDrawable(R.drawable.android_p_logo)) val thirdButton : ButtonData = ButtonData("Third", resources.getDrawable(R.drawable.android_studio_icon)) val buttonsData : ButtonsData = ButtonsData(listOf(firstButton, secondButton, thirdButton)) binding.buttonsData = buttonsData }เราสร้าง 3 instances ของ ButtonData แล้ว instantiate ButtonsData object และ attach กับ binding object
สุดท้าย ใช้ data class ใน layout ได้อย่างถูกต้อง:
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/linearLayout3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView2"> <ImageButton android:layout_width="100dp" android:layout_height="100dp" android:adjustViewBounds="true" android:scaleType="centerInside" android:src="@{buttonsData.get(0).buttonImageSrc}"> // <------- </ImageButton> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="@{buttonsData.get(0).buttonText}" // <-------- android:textSize="16sp" /> </LinearLayout>
บทความนี้เขียนจากประสบการณ์พัฒนาแอปนี้:
(อาจจะมีประโยชน์ก็ได้)
Laundry Symbols - Apps on Google Playเคยยุ่งยากกับสัญลักษณ์ซักผ้า? โชคดี! แอปนี้มีคำอธิบายสัญลักษณ์แปลกๆ ทั้งหมด พร้อม UI ง่ายและรูปภาพ
tomerpacificApps on Google Play
ดูซอร์สโค้ดเต็มได้ที่:
TomerPacific/LaundrySymbolsแอปพลิเคชันเขียนด้วย Kotlin แสดงสัญลักษณ์ซักผ้าต่างๆ พร้อมคำอธิบาย :cyclone: - TomerPacific/LaundrySymbols
TomerPacificGitHub