[Dev] มาดูวิธีทำ Data Binding บน Android แนวทางการเขียน App แบบใหม่กันหน่อย [Update 16/11/58]

Data Binding เป็นวิธีการเขียนโปรแกรมในอีกแนวทางหนึ่ง ซึ่งจะพบอยู่บน Visual Studio มานานแล้ว อย่างที่เคยเขียนบทความไว้ในตอน วิธีการทำ Data Binding กับ Object สำหรับ Windows Forms บน VB.NET เบื้องต้น นั่นเอง

Android-Developer-logo_Data-Binding

สำหรับบน Android นั้น ทาง Google ได้เตรียม Plug-in สำหรับ Android Studio ไว้ให้แล้ว เพียงแต่ยังไม่ออกเวอร์ชั่น Stable ซึ่งก็อาจจะแตกต่างจากในบทความนี้ แต่โดยส่วนตัวเชื่อว่า มันจะง่ายขึ้น ง่ายขึ้น และง่ายขึ้น เพราะตอนนี้ยังรู้สึกว่ายากอยู่ เมื่อเทียบกับการทำ Data Binding บน Visual Studio ที่ง่ายมาก ๆ

สำหรับ Data Binding นั้น ก็คือการผูกข้อมูล (Model) เข้ากับหน้าจอ (UI)

เพื่อความเข้าใจมากขึ้น ลองดู Code ต่อไปนี้…

สมมติว่ามี Class Employee ที่สามารถเก็บข้อมูลได้ 2 อย่าง คือ Name เป็นชนิด String และ Age เป็นชนิด int ดังนี้

Class Employee ก็คือ Model นั่นเอง…

แล้วเราก็มี EditText อยู่บน Layout ซึ่งเราก็ทำการ findViewById() มาซะ

ทีนี้ เราจะเอาข้อมูลจาก Model ใส่ไปยัง EditText สองตัวนี้ ด้วยท่ามาตรฐาน เราก็จะเขียนเป็น…

แล้วถ้าจะเอาค่าจาก EditText กลับคืนสู่ Model ล่ะ ก็…

น่าเบื่อไม๊ครับ ที่จะต้องมาทำอะไรแบบนี้อยู่ตลอด…

จาก Code ตัวอย่างด้านบน txtFullName ยังไงก็ผูกกับ Name ของ Class Employee และ txtAge ก็ผูกกับ Age ของ Class Employee อยู่เสมอ ซึ่ง Data Binding จะมาช่วยให้เราไม่ต้องมาเขียนคำสั่งอะไรที่ซ้ำซาก น่าเบื่อ แบบที่เห็นข้างบนนี้นั่นเอง

เริ่มน่าสนใจกันรึยังครับ!?

 

List of contents

 

Setup Environment

ก่อนอื่นเราต้องทำให้ Android Studio สามารถทำ Data Binding ได้ก่อน โดยจะต้องเพิ่ม Plug-in ไว้ในไฟล์ build.gradle ของ Project ดังนี้

โดย Plug-in จะใช้ได้กับ Gradle Version 1.3.0-beta4 ขึ้นไปเท่านั้น ในตัวอย่างด้านบน ผมจะใช้กับ Version 1.3.1

และไปเพิ่มคำสั่งต่อไปนี้ ใน build.gradle ของ Module ที่ต้องการทำ Data Binding

Update 16/11/58 : เนื่องจากมีการเปลี่ยนแปลงวิธีการเปิดใช้งาน Data Binding โดยมีการรวมความฟีเจอร์ Data Binding เข้ากับ Gradle Version 1.5.0-alpha1 แล้ว ซึ่งเมื่อวันที่ 12 พ.ย. ที่ผ่านมา ทาง Google ได้ปล่อย Gradle Version 1.5.0 ออกมาแล้ว เราสามารถไปกำหนด Version ได้ในไฟล์ build.gradle ของ Project ดังนี้

และในส่วนของ build.gradle ของ Module ให้เพิ่ม Block dataBinding ภายใน Block android ดังนี้

ดู Code การเปลี่ยนจากการเพิ่ม Data Binding Plug-in เป็นการใช้ Gradle ใหม่ ได้ที่ Commit นี้ บน GitHub

ทำการ Sync Gradle ให้เรียบร้อย ก็พร้อมทำ Data Binding แล้วครับ

 

Create Model

สมมติว่า ใน App เรา จะเล่นกับข้อมูลพนักงาน ที่มีชื่อ (name), อายุ (age) และ เพศ (gender) เราก็จะออกแบบ Class Employee ออกมา ดังนี้

เบื้องต้น เราจะออกแบบ Class Employee แค่นี้ก่อน

 

Create Layout

ในส่วนของหน้าจอ จะออกแบบ UI ดังนี้

Layout ตัวอย่าง
Layout ตัวอย่าง

จาก UI นี้ สมมติว่าเราต้องการให้สามารถแสดงผลและแก้ไขชื่อ (name), อายุ (age) และ เพศ (gender) ได้ และสามารถกดปุ่ม +1 เพื่อเพิ่มค่าอายุไปอีก 1 และปุ่ม Save เราจะนำค่าทั้ง 3 ตัวออกมาแสดง

ซึ่งวิธีการเดิม ๆ ก็จะเขียนด้วยท่าแบบนี้

ให้เวลาไล่ Code แพ๊บนึง น่าจะเขียนกันได้เน๊อะ

 

Binding Layout

เอาล่ะ ทีนี้เราจะมาปรับปรุง Layout กันก่อน เพื่อให้สามารถทำ Binding ได้

ขั้นตอนแรก เราจะนำ tag <layout> มาครอบ Layout เดิมของเรา ดังนี้

แล้วทำการเพิ่ม tag <data> ไว้ภายใน <layout> โดยภายใน <data> เราจะต้องกำหนดตัวแปรด้วย tag <variable> ดังนี้

จาก Code ด้านบน จะได้ตัวแปรชื่อ emp ซึ่งก็คือ Class Employee ที่เรา Design ไว้ก่อนหน้านี้นั่นเอง

ทีนี้ เราจะผูก EditText สำหรับแสดงผลชื่อ กับ name ใน Class Employee ก็สามารถทำได้แบบนี้เลย

เนื่องจาก Attribule text ของ EditText เก็บค่าเป็นชนิด String และ name ใน Class Employee ก็เป็น String เช่นกัน เพราะฉะนั้น สามารถผูกกันตรง ๆ แบบนี้ได้เลย

จำไว้เสมอว่า Data Binding บน Android ณ ตอนนี้ จะเป็นการ Bind แบบ 1 Way คือจาก Model ไปยัง View เท่านั้น

 

Binding Data

เมื่อเราทำส่วนของ Binding Layout เสร็จแล้ว ต้องทำการ Make Project ให้เรียบร้อย เพราะ Android Studio จะใช้ Plug-in ที่เราเพิ่มเข้ามา ทำการ Generate Class ให้เรา จาก Binding Layout ที่เราสร้างขึ้นมานั่นเอง

สมมติว่า ไฟล์ Layout ที่เราสร้าง มีชื่อไฟล์ว่า activity_main.xml เมื่อ Make Project แล้ว จะได้ Class ชื่อ ActivityMainBinding ขึ้นมาอัตโนมัติ โดยการนำชื่อไฟล์มาสร้างในแบบ Pascal case และต่อท้ายด้วยคำว่า Binding นั่นเอง และ Class จะอยู่ใน Package ที่เป็นชื่อ Package เดิมของเรา ต่อด้วย databinding เช่น com.artitk.android_databinding_example.databinding

สำหรับใน MainActivity จากที่เราเคยใช้ Method setContentView() ของ Activity แล้วระบุ id ของ Layout ลงไป ก็จะต้องเปลี่ยนเป็นการใช้ Method setContentView() ของ DataBindingUtil ซึ่งจะ Return มาเป็น Class ActivityMainBinding ดังนี้

แล้วก็ทำการผูก Data เข้ากับ Binding ได้เลย…

เสร็จแล้ว… View ที่เราทำไว้ ก็จะแสดงข้อมูลตาม Model ได้แล้วล่ะ

นอกจากนี้ ใน Class ActivityMainBinding จะทำการ Generate ทุก ๆ View ที่มี ID อยู่ใน Binding Layout ให้อัตโนมัติ ไม่จำเป็นต้อง findViewById() เองแล้ว อย่างใน Layout ข้างบน มี <Button> ที่กำหนด ID ไว้อยู่ เช่น android:id="@+id/btn_add_age1" ซึ่งใน ActivityMainBinding ก็จะสร้างตัวแปรชนิด Button ชื่อ btnAddAge1 ให้ โดยชื่อจะสร้างให้ในแบบ Camel case สามารถใช้งานได้เลย

สบ๊ายยยยยยยยยยยย~!!

 

Custom Binding Class Names

จากหัวข้อที่แล้ว จะเห็นว่า Class ActivityMainBinding ถูกสร้างขึ้นมาให้อัตโนมัติ ใน Package ที่ถูกกำหนดไว้แล้ว แต่ถ้าเราไม่ชอบ สามารถ Custom ได้เองด้วย โดยการไปเพิ่ม Attribute class ที่ tag <data> ในไฟล์ Layout ได้ ดังนี้

ก็จะได้ Class ชื่อ MainActivityBinding แทน และอยู่ที่ Package com.artitk.android_databinding_example.databinding

หรือถ้ากำหนด Attribute แบบนี้…

ก็จะได้ Class ชื่อ MainActivityBinding และอยู่ที่ Package เดียวกับ App ของเราเลย ก็คือ com.artitk.android_databinding_example

หรืออยากจะเปลี่ยนที่อยู่ของ Class ให้ไปอยู่ Package อื่นเลยก็ทำได้ โดยการกำหนด Attribute แบบเต็ม ๆ เลย แบบนี้

ก็จะได้ Class ชื่อ MyBinding และอยู่ที่ Package com.ethan นั่นเอง

 

Expression

ใน Binding Layout นั้น นอกจากจะระบุตัวแปรลงไปตรง ๆ แล้ว สามารถใส่ Expression ลงไปได้ด้วย เช่นจากตัวอย่างข้างบน age ใน Class Employee เป็นชนิด int ซึ่งไม่สามารถไปกำหนดให้ Attribute text ของ EditText ได้ ถ้าฝืนทำแบบนี้ จะเกิด Error ขึ้น

Error กรณี Binding ไม่ถูกต้อง
Error กรณี Binding ไม่ถูกต้อง

แต่เราสามารถใช้ Expression แบบนี้ลงไป เพื่อให้แปลงจาก int เป็น String ได้

สำหรับ Expression อื่น ๆ ที่สามารถใช้ได้ ก็มีดังนี้ครับ

  • Mathematical + - / * %
  • String concatenation +
  • Logical && ||
  • Binary & | ^
  • Unary + - ! ~
  • Shift >> >>> <<
  • Comparison == > < >= <=
  • instanceof
  • Grouping ()
  • Literals – character, String, numeric, null
  • Cast
  • Method calls
  • Field access
  • Array access []
  • Ternary operator ?:

ส่วน Expression ที่ไม่สามารถใช้ได้ จะมีดังนี้

  • this
  • super
  • new
  • Explicit generic invocation

และยังสามารถใช้ Null coalescing operator (??) ได้ เช่น ถ้าเราใช้ Ternary operator ตรวจสอบค่า null ดังนี้

สามารถใช้ Null coalescing operator แทนได้ ดังนี้

นอกจากนี้ ยังสามารถเข้าถึง View หรือ Resource อื่น ๆ ได้ด้วย อ่านเพิ่มเติมจาก Reference ท้ายบทความละกันนะ

 

Observable

ในตัวอย่างที่ทำมาด้านบน ถ้ายังจำกันได้ ปุ่ม +1 ผมต้องการให้เมื่อถูกกด จะไปเพิ่ม age ไปอีก 1 ด้วยท่ามาตรฐาน เขียนได้ดังนี้

ในบรรทัดที่ 5 เราต้องใส่ค่าให้กับ EditText เอง แต่ตอนนี้เราทำการ Binding ไว้ที่ Binding Layout เรียบร้อยแล้ว ก็ควรจะเหลือ Code แค่นี้

ลองรันดูซิครับ ค่า age มันถูก +1 ไปแล้ว แต่… EditText ข้อมูลไม่อัพเดตตาม!?

ไหนว่า 1 Way binding (Model to View) แล้วไง~!!

ก็เพราะว่า View ไม่รู้ ว่า Model มีการอัพเดตแล้ว เราจึงต้องทำการส่งสัญญาณไปบอก View ด้วย โดยใน Model จะต้องทำการ Inherit (extends) จาก Class BaseObservable ด้วย ดังนี้

แล้วทำการใส่ @Bindable ให้กับ Getter ซะ ถ้าให้ดีก็ Make Project ไปซักรอบนึง ตัว Plug-in จะทำการสร้าง Class BR ขึ้นมาให้ ซึ่งเราจะต้องใช้กำหนดให้กับ Method notifyPropertyChanged() เพื่อส่งสัญญาณไปบอก View นั่นเอง

ลองรันดูอีกที จะเห็นว่า เมื่อกดปุ่ม +1 ค่าใน EditText จะถูกอัพเดตตามแล้วล่ะ…

Update 16/9/58 : หากว่ามีการเปลี่ยนแปลงภายใน Model หลาย ๆ จุด พร้อม ๆ กัน สามารถใช้ Method notifyChange() แทนได้ โดยไม่ต้องใส่ @Binable ให้กับ Getter ที่เกี่ยวข้อง

 

นอกจาก BaseObservable แล้ว ยังมีอีก Class ที่น่าสนใจ นั่นก็คือ ObservableField

สมมติว่า เรามี Model แบบนี้

ก็คือ มี Field ชื่อ name และมี Getter, Setter ในท่ามาตรฐาน ซึ่งเราสามารถเปลี่ยนไปใช้ ObservableField ได้ โดยเปลี่ยนเป็นแบบนี้…

ส่วนเวลาใช้งาน ก็จะเป็น…

ก็คือ เปลี่ยนจากการเรียกใช้ Getter, Setter Method ที่เราต้องสร้างขึ้นมา ไปใช้ Method get() และ set() ของ ObservableField นั่นเอง

 

Binding Model to View

ทั้งหมดที่เราทำ Data Binding มาในหัวข้อก่อน ๆ นั้น จะเป็นการ Bind แบบ Model to View อย่างเดียว ซึ่งหลังจากผูก Model กับ View แล้ว และ Set Object ที่สร้างจาก Model ให้กับ Binding Class แล้ว View ก็จะแสดงข้อมูลตาม Model ได้แล้ว รวมถึงเมื่อข้อมูลใน Model เปลี่ยน View ก็อัพเดตตามได้แล้ว ด้วยการส่งสัญญาณ ผ่าน Method notifyPropertyChanged() แล้วนั้น สรุปออกมาเป็น Code อีกครั้ง ดังนี้

ส่วนของ Layout

ส่วนของ Model

ส่วนของ Activity

ทีนี้ ถ้าเราอยากให้การเปลี่ยนแปลงที่เกิดขึ้นบน View เช่น User ไปแก้ไขข้อมูลใน EditText แล้วอยากให้ Model เปลี่ยนค่าตามล่ะ จะทำยังไง… ดูหัวข้อต่อไปกันเลยครับ

 

Binding View to Model

สำหรับการทำ Data Binding ในเส้นทาง View to Model นั้น จะไม่เหมือนของฝั่ง Visual Studio ที่ว่า ถ้า Type ตรงกัน จะเป็น 2 Way binding ได้เลย แต่บน Android การทำ 2 Way binding ในส่วนของ View to Model เราจำเป็นจะต้องออกแรงเยอะหน่อย

ในกรณีของ EditText ที่เราเคยเขียนคำสั่งไว้ใน Activity แบบนี้…

ก็คือ เราจะต้องสร้าง TextWatcher ขึ้นมา เพื่อเก็บข้อมูลจาก Event afterTextChanged() ก็จะต้องเปลี่ยนเป็น…

 

!!!!!

ถูกแล้วครับ คือ… ไม่ต้องเขียนอะไรใน Activity เลย แต่ว่าเราจะไปเขียนที่ Model แทน แบบนี้

เพราะฉะนั้น ใน Model เราจะมี Getter Method เพิ่มขึ้นมา 1 ตัว ก็คือ getNameWatcher() ซึ่งจะ Return ออกไปเป็น TextWatcher นั่นเอง

Update 16/9/58 : ควรตรวจสอบว่าข้อมูลมีการเปลี่ยนแปลงก่อนที่จะเก็บค่าใหม่ลงไปด้วย ดังตัวอย่างในบรรทัดที่ 6 เนื่องจากว่า หากนำไปใช้ร่วมกับ ObservableField แล้ว เมื่อ View มีการเปลี่ยนแปลง Model เปลี่ยนค่าตามที่เรากำหนด ซึ่ง ObservableField จะมีการ Notify ไปบอก View ว่ามีการเปลี่ยนแปลง ก็จะเกิด Event ซ้ำอีกแบบไม่รู้จบ เกิดเป็น Infinite cycle ขึ้นได้ แนะนำว่า ควรตรวจสอบและทดสอบให้ดี เพราะการใช้ ObservableField กับ Listener บางตัว เช่น TextWatcher นอกจากแก้ปัญหาเรื่อง Infinite cycle แล้ว แต่ยังพบปัญหาเรื่องการเปลี่ยนตำแหน่งของ Curser ใน EditText อีก จึงไม่ค่อยแนะนำให้ใช้งาน ObservableField ในตอนนี้ แต่จากการทดสอบ สามารถใช้งานร่วมกับ OnCheckedChangeListener ของ RadioButton ได้ เนื่องจาก OnCheckedChangeListener จะไม่เกิดซ้ำหากมีการเลือก RadioButton ตัวเดิม

แล้วที่ Binding Layout จะต้องเพิ่ม Custom Attribute ไป 1 ตัว คือ app:addTextChangedListener ดังนี้

ชื่อ Custom Attribute app:addTextChangedListener ถ้าสังเกตุซักนิด จะเห็นว่า มันคือชื่อ Method ที่เราเคยเรียกใช้ (txtFullName.addTextChangedListener()) นั่นเอง ส่วนค่าที่กำหนดให้กับ Custom Attribute ที่เห็นด้านบน ก็คือ @{emp.nameWatcher} นั้น ก็คือ Getter Method getNameWatcher() ที่เราเพิ่มไว้ โดยการตัดคำว่า get ออก แล้วเปลี่ยนชื่อเป็นแบบ Camel case นั่นเอง

เท่านี้เราก็สามารถทำ Data Binding ขา View to Model ได้แล้วล่ะ เมื่อทำร่วมกับขา Model to View ก็จะเป็น 2 Way binding ได้แล้วววววว

Update 16/9/58 : ไม่ต้องทำ Custom Attribute ก็ได้ แต่จะเจอ Warning เพิ่มขึ้น เนื่องจากเป็น Attribute ที่ Android Studio ไม่รู้จัก แต่ทำงานได้ ไม่เกิด Error ใด ๆ

 

Update 16/9/58 : สำหรับการทำ Data Binding บางครั้งจะเกิด Error แปลก ๆ เนื่องจากยังไม่ค่อยเสถียรกับ Android Studio อาจจะต้องปิด/เปิด Android Studio กันใหม่ ล่าสุดได้ทดสอบกับ Android Studio 1.4 Beta 4 ยังพบปัญหาอยู่ ส่วนใหญ่เกิดจากการเปลี่ยน Git Branch ไปมา กับ Branch ที่ไม่ได้ทำ Data Binding ก็เลยเหมือนกับการเอา Plug-in เข้า ๆ ออก ๆ แต่ถ้าปกติ ไม่น่าจะเกิดปัญหานี้

สำหรับการทำ Data Binding นั้น ยังต้องใช้ Plug-in ที่ต้องใส่เพิ่มเอาเองตามที่เห็นในหัวข้อแรก ๆ และยังไม่ใช่ Final version เพราะฉะนั้น ควรใช้อย่างระมัดระวัง เพราะเมื่อถึงเวลาออกเวอร์ชั่นจริงแล้ว อาจมีการเปลี่ยนแปลงไปจากนี้ หรืออาจจะไม่เปลี่ยนแปลงเลย ความเห็นส่วนตัวผมคิดว่าไม่น่าจะเปลี่ยนแล้ว หรืิอถ้าเปลี่ยนก็คงเล็กน้อย ดู ๆ ไว้หน่อยก็ดีนะครับ เมื่อถึงเวลาออกเวอร์ชั่นจริง จะมาเขียนสรุปสิ่งที่เปลี่ยนแปลงไปให้อ่านกันนะ

ส่วนตัวอย่างที่ทำขึ้นมาเพื่อเขียนประกอบในบทความนั้น เพื่อใช้อธิบายในแต่ละจุดที่สำคัญ ๆ แต่ Code ที่สมบูรณ์ ผมใส่ไว้ให้แล้วที่ GitHub ลองตามไปดูเพิ่มเติมกันได้นะ และสามารถศึกษาเพิ่มเติมใน Link ด้านล่างนี้ และมีพูดถึง MVVM Design Pattern กันเลยทีเดียว เพราะ MVVM จะใช้เทคนิคการทำ Data Binding นี่แหล่ะ

 

อ้างอิงและเพิ่มเติม

Leave a Reply

Your email address will not be published. Required fields are marked *

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.