View Binding บนแอนดรอยด์ทำแบบไหนได้บ้างนะ?

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

ซึ่งในบทความนี้ขอพูดถึงเฉพาะ Kotlin นะ ลืมวิธีเขียนโค้ด Java ไปแล้ว 😂

View Binding สามารถทำได้หลายแบบ

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

วิธีแบบพื้นฐานที่สุด

นั่นคือการทำ View Binding ด้วยคำสั่ง findViewById นั่นเอง ซึ่งเป็นวิธีดั้งเดิมที่สุดของการพัฒนาแอนดรอยด์เลยก็ว่าได้

// MainActivity.kt
class MainActivity : AppCompatActivity() {
    private lateinit var textViewName: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        textViewName = findViewById(R.id.textViewName)
        textViewName.text = "Sleeping For Less"
    }
}

ถ้าอยากจะเขียนให้ดูดีมากขึ้นกว่านี้ สามารถใช้ Lazy ของ Kotlin ได้นะ จบที่บรรทัดเดียวได้เลย และจะได้ประกาศเป็น val ด้วย

// MainActivity.kt
class MainActivity : AppCompatActivity() {
    private val textViewName: TextView by lazy { findViewById<TextView>(R.id.textViewName) }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        textViewName.text = "Sleeping For Less"
    }
}

Butter Knife Library

เป็น Library ของ Jake Wharton ที่ใช้ความสามารถของ Annotation Processing เพื่อสร้างโค้ดสำหรับ View Binding เพื่อลดโค้ดให้น้อยลง

// build.gradle (Module: app)
...
apply plugin: 'kotlin-kapt'

android {
    ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    ...
    implementation 'com.jakewharton:butterknife:10.2.0'
    kapt 'com.jakewharton:butterknife-compiler:10.2.0'
}

โดย Butter Knife จะลดโค้ดจากเดิมให้เหลือเพียงแค่ Annotation สั้นๆ ที่อ่านแล้วเข้าใจได้ง่าย แต่มีเงื่อนไขว่าต้องประกาศคำสั่ง ButterKnife.bind(...) ไว้ใน onCreate(...) ด้วยทุกครั้ง

class MainActivity : AppCompatActivity() {
    @BindView(R.id.textViewName)
    private lateinit var textViewName: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ButterKnife.bind(this)
        
        textViewName.text = "Sleeing For Less"
    }
}
ล่าสุดผู้พัฒนา Butter Knife ได้แนะนำให้เปลี่ยนไปใช้ View Binding แทนแล้ว

จริงๆแล้ว Butter Knife ยังมีความสามารถอื่นๆอีก แต่ในบทความนี้จะขอพูดถึงแค่ View Binding เท่านั้น

Kotlin Synthetic ใน Kotlin Android Extensions

ถ้าผู้ที่หลงเข้ามาอ่านใช้ Kotlin ในโปรเจค จะมี Kotlin Synthetics ให้ใช้ โดยจะต้องเพิ่ม Kotlin Android Extensions ไว้ในโปรเจคนั้นๆด้วยการเพิ่ม Plugin ดังกล่าวไว้ใน build.gradle

// build.gradle (Module: app)
...
apply plugin: 'kotlin-android-extensions'

android {
    ...
}

dependencies {
    ...
}

ความสามารถดังกล่าวจะทำให้นักพัฒนาสามารถเรียกชื่อ View นั้นๆได้เลย โดยจะต้อง Import คลาสของ Kotlin Synthetics ไว้ข้างบนด้วย ซึ่งจะมีการอ้างอิงไปถึงชื่อ Layout XML ที่ต้องการเรียกใช้งาน

// MainActivity.kt
...
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        textViewName.text = "Sleeping For Less"
    }
}

ถึงแม้ว่าจะ Import ชื่อ Layout XML ผิด แต่ถ้า View มีชื่อ ID เหมือนกันก็ยังสามารถใช้ได้ แต่ก็ไม่ใช่สิ่งที่ควรทำนะ

Data Binding Library

เป็น Library ตัวหนึ่งที่อยู่ใน AndroidX ของทาง Google ที่มีจุดเด่นในเรื่องการทำ Two-way Data Binding และการทำ View Binding ก็เป็นหนึ่งในความสามารถของ Library ตัวนี้เช่นกัน

ในการใช้งาน Data Binding จะมี Compiler แบบเก่า (v1) และแบบใหม่ (v2) ซึ่งเจ้าของบล็อกขอพูดถึงเฉพาะ v2 นะ เพราะมันออกมานานมากพอจนที่ v1 แทบจะไม่มีคนใช้แล้ว

การใช้งาน Data Binding สามารถทำได้ง่ายๆเพียงแค่เปิดใช้งานผ่าน build.gradle

// build.gradle (Module: app)
...
android {
    ...
    dataBinding {
        enabled = true
    }
}

dependencies {
    ...
}

และต้องกำหนดใน Gradle Properties ด้วยว่าจะใช้ Compiler เป็น v2

// gradle.properties
...
android.databinding.enableV2=true

Layout XML ที่ใช้กับ Data Binding จะต้องใส่ <layout /> ครอบไว้ด้วย

<!-- activity_main.xml -->

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".MainActivity">

    <data> ... </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        ...

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

และในส่วนของ View Binding ก็จะต้องใช้ DataBindingUtil เพื่อกำหนด Layout XML ให้กับ Activity ที่ต้องการ แล้วจะได้ออกมาเป็น Binding Class เพื่อเรียก View ผ่านคลาสนั้นๆ

// MainActivity.kt

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.textViewName.text = "Sleeping For Less"
    }
}

View Binding

อ่านมาถึงตรงนี้อาจจะแปลกใจกับชื่อที่ใช้เรียก แต่เค้าใช้ชื่อนี้จริงๆนะ ซึ่ง View Binding เป็นความสามารถที่ทีมพัฒนาได้ใส่มาใน Android Studio 3.6 และใช้ได้กับ Android Gradle Plugin 3.6.0 ขึ้นไปเท่านั้น

// build.gradle (Module: app)

...

android {
    ...
    viewBinding {
        enabled = true
    }
}

dependencies {
    ...
}

และเวลาเรียกใช้งานผ่านโค้ดก็สามารถเรียกผ่าน Binding Class ได้เลย ไม่ต้องทำอะไรเพิ่มเติมกับ Layout XML เหมือนแบบ Data Binding

// 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)
        
        binding.textViewName.text = "Sleeping For Less"
    }
}

แล้ววิธีแบบไหนดีกว่ากันล่ะ?

สุดท้ายก็คงไม่พ้นคำถามว่านักพัฒนาควรใช้วิธีแบบไหนถึงจะดีที่สุด เพื่อให้เปรียบเทียบได้อย่างชัดเจน ให้ดูตารางข้างล่างนี้ได้เลย

ข้อมูลจากงาน Google I/O 2019

การใช้ findViewById จะมีข้อเสียคือเกิด Boilerplate Code ขึ้นเยอะมากเมื่อเทียบกับวิธีอื่น แถมเสี่ยงต่อการเรียกใช้งาน View ผิดตัวได้ง่าย

ส่วน Butter Knife จะมาช่วยแก้ไขเรื่อง Boilerplate Code ที่เคยมีใน findViewById แต่ก็ยังมีโอกาสเรียก View ผิดตัวได้เหมือนเดิม และเนื่องจากใช้ Annotation Processing ในการช่วยลด Boilerplate Code จึงทำให้ Compile Time นานกว่าปกติ

สำหรับ Kotlin Synthetic นั้นเข้ามาจัดการเรื่องโค้ดที่สั้นลงอย่างเห็นได้ชัด และไม่มีผลต่อ Build Speed ด้วย แต่ยังไม่มี Type Safety จึงทำให้มีโอกาสเรียกใช้ View ผิดตัวได้อยู่

Data Binding ช่วยเรื่องลดจำนวนโค้ดได้เป็นอย่างดี แต่ฝั่ง Layout XML กลับต้องเขียนเพิ่ม และจุดประสงค์ของ Data Binding ก็คือการทำ Two-way Data Binding ดังนั้นถ้าใช้แค่เพื่อทำ View Binding อย่างเดียวก็ไม่เหมาะซักเท่าไร และที่สำคัญ Data Binding มี Build Speed ที่ช้าขึ้น

และ View Binding น่าจะเป็นพระเอกที่สุดในเรื่องนี้ เพราะเข้ามาแก้ปัญหาที่มีอยู่ในแต่ละวิธี ซึ่งจริงๆแล้ว View Binding ก็ใช้หลักการเดียวกับ Data Binding แต่ตัดในส่วนของ Data ออกให้เหลือแต่ View จึงทำให้วิธีนี้กว่า Data Binding ถ้าต้องการทำแค่ View Binding เท่านั้น ติดแค่ว่ารองรับเฉพาะ Android Studio 3.6 ขึ้นไปและต้องเป็น Android Gradle Plugin 3.6.0 ซึ่งตอนนี้ Android Studio ยังคงเป็น Preview และ Alpha อยู่ จึงยังไม่แนะนำให้นำไปใช้งานจริง

สรุป

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

ถ้าต้องการทำ Two-way Data Binding อยู่แล้ว การเลือกใช้ Data Binding ก็เป็นวิธีที่เหมาะสม หรือถ้าเป็นโปรเจคไม่ได้ยิ่งใหญ่หรือซับซ้อนอะไรมากนัก แค่ใช้ Kotlin Synthetic ก็เพียงพอแล้วเช่นกัน

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