Hello ViewBinding, goodbye findViewById

Người ta thường nói rằng các lập trình viên đều lười biếng và đó (thường) là một điều tốt. Nó có nghĩa là thay vì lặp đi lặp lại các công việc hay các đoạn code, các lập trình viên thường tìm cách để tránh phải lặp đi lặp lại như thế và tối ưu thời gian của mình. Các lập trình viên Android trong nhiều năm qua đã cố gắng tránh boiler plate code liên quan tới findViewById. Hãy tưởng tượng ta có một layout như sau:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/mainTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:padding="16dp"
        android:textSize="18sp"
        tools:text="Main Title" />

    <TextView
        android:id="@+id/subTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/mainTitle"
        android:gravity="center"
        android:padding="16dp"
        android:textSize="14sp"
        tools:text="Main Subtitle" />
</RelativeLayout>

Khi chúng ta cần truy cập vào các view ở trên và sử sử dụng trong Java code, ta thường làm công việc tương tự như sau:

public class MainActivity extends AppCompatActivity {
    private TextView txtViewMainTitle;
    private TextView txtViewSubTitle;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        txtViewMainTitle = findViewById(R.id.mainTitle;
        txtViewSubTitle = findViewById(R.id.subTitle);
                                        
        txtViewMainTitle.setText("This is my main title");
        txtViewSubTitle.setText("This is my subTitle");
    }
}

Trông thật quen thuộc đúng không?

findViewById - "the old way"

Để có thể tránh các đoạn boiler plate code này, nhiều lập trình viên bắt đầu sử dụng thư viện Butter Knife, hay cũ hơn là RoboGuice (một Dependency injection framework cho phép ta inject các view, hiện tại đã không còn được hỗ trợ). Butter Knife hiện tại vẫn còn được sử dụng trong rất nhiều ứng dụng và GitHub của nó vẫn còn hoạt động. Tuy nhiên, việc phát triển thư viện có lẽ sẽ sớm dừng lại. Nếu bạn đã sử dụng Kotlin trong ứng dụng của bạn, chắc hẳn bạn đã sử dụng tới Kotlin Android Extensions. Các thư viện được nhắc tới ở trên đều yêu cầu một trường annotation cho mỗi view được expose. Kotlin Android Extension dễ sử dụng hơn rất nhiều, bởi chúng cho phép ta sử dụng cùng kết quả có được từ các thư viện trên mà không cần phải thêm vào bất kỳ đoạn code nào. Ví dụ:

import kotlinx.android.synthetic.main.activity_main.*

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

        mainTitle.text = "This is my main title"
        subTitle.text = "This is my subTitle"
    }
}

Truy cập vào các view trông đã đơn giản hơn rất nhiều. Tuy nhiên, với ViewBinding, mọi thứ còn tốt hơn nữa.

findViewById — “the new (cool😎) way”

ViewBinding được giới thiệu tại Google I/O 19 đưa ra một cách nhanh hơn, đơn giản hơn, type/null safe để bind các view. Tại thời điểm này, nó vẫn là một tính năng thử nghiệm và chỉ có trên Android Studio 3.6 Canary 11+ Sau khi tải về và cài đặt Android Studio Canary, để có thể sử dụng ViewBinding, ta cần enable nó trong file build.grade:

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

Mặc dù tài liệu chính thức không nhắc tới, nhưng ta cũng cần phải upgrade Android Studio Gradle Plugin lên bản 3.6.0-alpha11 hoặc cao hơn.

buildscript {
    ext.kotlin_version = '1.3.50'
    repositories {
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.6.0-alpha12'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

Lưu ý rằng phiên bản của Gradle được sử dụng cần phải là 5.6.1 hoặc cao hơn. Hãy xem thử file gradle-wrapper.properties và kiểm tra phiên bản của Gradle đã đảm bảo chưa. Sau đó, ta đã có thể sử dụng ViewBinding. Tiêp tục xem xét ví dụ trước đó, khi sử dụng ViewBinding, code của chúng ta sẽ như sau:


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

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.mainTitle.text = "This is my main title"
        binding.subTitle.text = "This is my subTitle"
    }
}

Tên của class binding được generate bằng cách convert tên của file XML thành kiểu camel case và thêm hậu tố "Binding". Do đó, trong trường hợp này tên của nó là ActivityMainBinding. Class ActivityMainBinding có hai trường: một TextView được gọi là mainTitle và một TextView khác có tên là subTitle. Bất kỳ View nào trong file layout không được gán id sẽ không được tham chiếu tới trong class binding. Để có thể truy cập vào root view của file layout, mỗi class binding sẽ bao gồm một phương thức getRoot(). Trong ví dụ của này, phương thức getRoot() của ActivityMainBinding sẽ trả về một RelativeLayout. Class binding được tạo ra chứa một phương thức static là ActivityMainBinding.inflate(LayoutInflater) cho phép tạo một instance của class. Về cơ bản, việc nó làm là inflate toàn bộ layout hierarchy để truy cập vào các views. Sau đó, ta chỉ cần gọi tới phương thức setContentView và truyền vào root view để tạo ra view đó trên màn hình.

Ưu điểm

Tính năng ViewBinding có một số ưu điểm khi so sánh với cách tiếp cận truyền thống cũng như là khi so sánh với một vài thư viện khác: *** Null safety:** view binding tạo tham chiếu trực tiếp tới các views, do đó không có khả năng một NullPointerException xảy ra do một view ID không hợp lệ. Cùng với đó, khi một view chỉ tồn tại với một số điều kiện đặc biệt, trường chứa tham chiếu tới nó trong binding class sẽ được đánh dấu với annotation @Nullable. *** Type safety:** tất cả các trường view binding được generater match cùng type với tham chiếu của nó trong XML, do đó không cần phải ép kiểu. Điều đó có nghĩa là rủi ro xảy ra ClassCastException sẽ thấp hơn. Trong trường hợp nếu vì một lý do nào đó layout và code không match với nhau, build sẽ fail tại compile time thay vì tại runtime. *** Speed:** ViewBinding không cần thêm thời gian build do nó không sử dụng annotation processor like ButterKnife hoặc DataBinding.

ViewBinding vs DataBinding

Tại thời điểm này, có thể bạn đang tự hỏi rằng ViewBinding và DataBinding có giống nhau không? Cả hai đều generate class binding để ta có thể sử dụng tham chiếu tới view một cách trực tiếp, nhưng vẫn có một số điểm khác biệt.

  • Để sử dụng DataBinding ta cần thêm tag layout vào file XML của layout.
  • ViewBinding không support layout variable hay layout expression.

Summary

Trên đây là một chút giới thiệu về ViewBinding . Hi vọng sẽ mang lại kiến thức bổ ích cho mọi người. Cảm ơn vì đã dành thời gian để đọc bài viết của mình. Tài liệu tham khảo: