Tìm hiểu về data Binding trong android

Trong sự kiện Google I/O 2015. Google đã giới thiệu đến một thư viện xử lý dữ liệu giữa tầng hiển thị và tầng dữ liệu có tên là Data Binding. Và cách sử dụng thư viện Data Binding này như sử dụng parttern Model-View-ViewModel (MVVM).Hiện tại thì thư viện Data Binding vẫn đang trong quá trình beta, có thể những phần tôi nói ở dưới sẽ bị thay đổi hoặc lỗi thời. Nếu có nghi ngờ hãy tham khảo ở tài liệu chính thức tại ĐÂY.

Lưu ý : Data Binding hiện tại chỉ là thư viện thử nghiệm. Vì vậy có thể bị loại bỏ khi google không cần đến. và các tài liệu có thể không được chính xác trong tương lai.

Trong quá trình tìm hiểu. Thì tôi cũng thấy một vài ví dụ về Data Binding. Code ví dụ mà tôi tìm hiểu có trên github. Source ví dụ bao gồm.

  1. Thêm thư viện Data Binding vào Android Studio.(phần này chỉ thêm một dòng vào trong các tập tin Gradle).
  2. Tạo một Plain Old Java object (POJO).
  3. Thực thi Data Binding vào trong các file giao diện. (Tôi thêm data binding bằng cách thêm một số meta-data/markup vào trong file layout của tôi.)
  4. Cập nhật Activity để khai báo data binding. (Tôi sẽ sử dụng data binding để liên kết tầng view với tầng POJO).

Bây giờ chúng ta sẽ đi vào với các nội dung của từng phần :

1. Thêm thư viện Data Binding vào Android Studio

Trước tiên, bạn hãy chắc chắn rằng phiên bản Android Studio đang là 1.3 hoặc cao hơn. Tiếp theo tôi sửa file build.gradle, ở phần dependencies của tôi sẽ trông như thế này :

     dependencies {
        classpath 'com.android.tools.build:gradle:1.3.1'
        // TODO: when the final verison of dataBinder is release, change this to use a version number.
        classpath 'com.android.databinding:dataBinder:1.+'
    }

Sau đó, tôi cập nhật tại file build.gradle của module app. thêm apply plugin:'com.android.databinding' vào ngay phía dưới dòng đầu tiên của file.

    apply plugin: 'com.android.application'
    apply plugin: 'com.android.databinding'

Và bây giờ, project của chúng ta đã có thể sử dụng thư viện Data Binding.

2. Sử dụng Data Binding

Từ đây, bạn có thể bớt khá nhiều khi tìm hiểu Google's docs on data binding. Nếu bạn biết data binding trong XAML(trong Windows Presentation Foundation (WPF hoặc Xamarin.Forms). Hãy nghĩ lại về ràng buộc trực tiếp đến data model của bạn. Đây sẽ là cơ hội hoàn hảo mang lại một vài tiện ích từ parttern Model-View-ViewModel vào trong ứng dụng Android của bạn. Mặc dù tôi cũng không nói nhiều về MVVM.

3. Tạo các Data Model (POJO)

Để giữ giao diện của tôi không phải thay đổi nhiều. Tôi abstract hết tất cả các logic sử lý data binding vào trong các class sau

    public class PhonewordViewModel extends BaseObservable {
            private final String TAG = PhonewordViewModel.class.getName();
        private boolean mIsTranslated = false;
        private String mPhoneNumber = "";
        private String mPhoneWord = "";
        private String mCallButtonText = "Call";

        @Bindable
        public String getPhoneNumber() {
            return mPhoneNumber;
        }

        @Bindable
        public String getCallButtonText() {
            return mCallButtonText;
        }

        @Bindable
        public boolean getIsTranslated() {
            return mIsTranslated;
        }

        @Bindable
        public String getPhoneWord() {
            return mPhoneWord;
        }

        public void setPhoneWord(String phoneWord) {
            mPhoneWord = phoneWord;
            notifyPropertyChanged(congnt.framgia.com.tutdatabinding.BR.phoneWord);
        }

        public void translatePhoneWord() {
            mPhoneNumber = toNumber(mPhoneWord);

            if (TextUtils.isEmpty(mPhoneNumber)) {
                mCallButtonText = "Call";
                mIsTranslated = false;
            } else {
                mIsTranslated = true;
                mCallButtonText = "Call " + mPhoneNumber + "?";
            }
            notifyPropertyChanged(congnt.framgia.com.tutdatabinding.BR.phoneNumber);
            notifyPropertyChanged(congnt.framgia.com.tutdatabinding.BR.isTranslated);
            notifyPropertyChanged(congnt.framgia.com.tutdatabinding.BR.callButtonText);
        }

    }

Ở đây tôi đã đóng gói logic xử lý data vào trong class được xem là class con của đối tượng BaseObservable. Việc làm Subclassing này là không bắt buộc. Để một POJO không cần extends vẫn có thể làm việc được.

Observable interface có cơ chế thêm và loại bỏ việc lắng nghe sự kiện, nhưng có notifying ra bên ngoài. Để làm đơn giản hơn trong quá trình phát triển, có một Base Class tên là BaseObservable được tạo ra để thực hiện cơ chế đăng ký lắng nghe. Các lớp Data Model implement BaseObservable vẫn có trách nhiệm thông báo khi các thuộc tính thay đổi. Điều này được thực hiện bằng cách gán một Annotation @Bindable phía trên getter và thông báo thay đổi trong setter bằng cách gọi hàm notifyPropertyChanged.

Một BR class sẽ tự động tạo ra bởi thư viện Data Binding. Class này để binding dữ liệu với file R.java để thông qua layout. với mỗi field hay method của Data Model đều đính kèm một Annotation @Bindable, khi đó sẽ có một hằng số tương ứng với tên gọi của field hay method được khai báo trong BR class tại thời điểm biên dịch. Như ở class PhonewordViewModel thì getters getPhoneNumber() sẽ trở thành BR.phoneNumber.

Sau khi chúng ta thiết kế Data Binding trong Model. Bây giờ sẽ là đến việc thực thi tại file layout.

4. Thiết lập trong file layout

Trong các file layout có sẵn để thực hiện data binding thì cần phải thay đổi một vài thứ như :

  • Khai báo biến trong layout.

Data Binding trong file layout yêu cầu phải nằm ở phần root element của file layout. ở file layout của tôi tôi bắt đầu với root element là <LinearLayout>. Add thêm element <data /> để khai báo các variable và các class data model sẽ bị ràng buộc.

    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
            <data>
                <variable
                name="phonewordVM"
                type="congnt.framgia.com.tutdatabinding.PhonewordViewModel" />
            </data>
    </layout>

Đoạn code trên là định nghĩa giá trị phonewordVM để tôi sử dụng trong file layout. Lưu ý rằng thuộc tính xmlns:app="http://schemas.android.com/apk/res-auto" sẽ tự động tạo namespaces vào trong file xml. Điều này giúp bạn vì bạn không cần phải khai báo rõ ràng các namespace trong file layout.

  • Xác định các thuộc tính vào các UI Widget (TextView , Button , EditText v.v.) để ràng buộc với biến khai báo ở bên trên.

Tiếp theo, tôi sẽ thiết lập các binding. Trong ví dụ lần này, tất cả việc tôi muốn làm là để bind setName()/getName() ở trong POJO của tôi vào trong EditText. Một đoạn XML ở bên dưới sẽ cho thấy việc binding giữ model và views :

    <EditText
            android:id="@+id/phoneword_text"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:hint="@string/phoneword_label_text"
            android:text="@{phonewordVM.phoneWord}"
            tools:ignore="TextFields" />

5. Thiết lập Data Binding

Đây là bước cuối cùng để thiết lập các Data Binding. Bây giờ chúng ta sẽ không cần phải sử dụng các tham chiếu đến một view. và truy vấn các thuộc tính trong view . Sau đó tự chuyển các giá trị trong view. Sau đó phải tự chuyển giá trị của view với các object hoặc giá trị trong ứng dụng của mình nữa. ở dưới là một đoạn trích từ fragment của tôi :

    public class MainActivityFragment extends Fragment {

        private PhonewordViewModel mPhonewordViewModel;
        private FragmentMainBinding mBinding;

        public MainActivityFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            mPhonewordViewModel = new PhonewordViewModel();

            mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false);
            mBinding.setPhonewordVM(mPhonewordViewModel);
            View v = mBinding.getRoot();

            mBinding.callButton.setOnClickListener(
                    new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            final Intent callIntent = new Intent(Intent.ACTION_CALL);
                            AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
                            alertDialogBuilder
                                    .setMessage(mBinding.callButton.getText())
                                    .setNeutralButton(R.string.call_button_text, new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            callIntent.setData(Uri.parse("tel:" + mPhonewordViewModel.getPhoneNumber()));
                                            PhonewordUtils.savePhoneword(getActivity(), mPhonewordViewModel.getPhoneWord());
                                            startActivity(callIntent);
                                        }
                                    })
                                    .setNegativeButton(R.string.cancel_text, new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            // Nothing to do here.
                                        }
                                    })
                                    .show();
                        }
                    }
            );

            mBinding.translateButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mPhonewordViewModel.setPhoneWord(mBinding.phonewordText.getText().toString());
                    mPhonewordViewModel.translatePhoneWord();
                }
            });

            return v;
        }
    }

Có một vài chỗ quan trọng cần lưu ý ở đây. Đầu tiên hãy nhìn trong hàm onCreateView của fragment. bình thường thì chúng ta sử dụng ngay tham số LayoutInflater được truyền vào trong hàm. Nhưng ở đây tôi sử dụng class DataBindingUtil để inflater layout fragment_main.xml.Thư viện Data Binding sẽ tạo ra một class có tên là FragmentMainBinding. Cái tên của class sẽ bắt đầu từ file layout và công việc của Binding là nối vào với nó. Khi binding được khởi tạo. tôi sẽ nói với nó sẽ có những đối tượng để gán vào. Thư viện Data Binding tạo ra setter setPhonewordVM - Điều này bởi vì tôi đã định nghĩa một giá trị phonewordVM ở trong file layout phía trên. Điều thú vị ở đây là trong class fragment không còn sử dụng đến method findViewById hoặc giữ một tham chiếu đến bất kì một component view nào. Đó là bởi vì FragmentMainBinding đã chứa những tham chiếu đó rồi. Vì vậy nếu tôi muốn lấy giá trị của một EditText có id là @+id/phonewordText thì tôi chỉ việc sử dụng mBinding.phonewordText.getText() và làm các việc còn lại.

6.The End

Với tất cả các mục trên. Việc sử dụng thư viện Data Binding đã được hoàn thành. Nó có thể có rất nhiều mã và có lẽ đây là một ví dụ nhỏ. Sức mạnh thực của việc này là khi bạn muốn viết test code của bạn. Dữ liệu 2 chiều Data Binding đưa ra một framework cho Model-View-View Model.