注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

和申的个人主页

专注于java开发,1985wanggang

 
 
 

日志

 
 

Getting Started with Data Binding in Android  

2015-12-10 14:33:07|  分类: 安卓 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

(Note: This is an updated version of a post from June, 2015)

In May, 2015 at Google announced a data binding library for Android. The data binding library is currently in beta, so things might change and make what I am saying here irrelevant/obsolete. When in doubt, consult the official documentation.

It's long overdue – developers no longer have to come up with their own schemes for displaying or retrieving data from their views. With two-way data binding, it's possible to remove a lot of redundant boilerplate code from the activities and fragments that make up an application.

There were several steps/phases when I went through while I was learning this. Here's what I did:

Just a warning - Android data binding is still a beta product, so as such things may or may not work when they should, and the documentation may or may not be accurate.

The source code for this sample is up on Github.

  1. Add data binding to Android Studio – This is a one time thing, a couple of lines in some Gradle files.
  2. Create a POJO for the binding – You don't necessarily want to bind to a domain object. Arguable it's a cleaner design to have another class with responsiblity of data binding (and maybe some validation too). Model-View-ViewModel is an excellent pattern in this regard.
  3. Update the layout file – We help the data binding library out by adding some meta-data/markup to our layout files.
  4. Update the activity to declare the data binding – This will tell the data binding library how to connect the views to the POJO.

Adding Data Binding to Your Project

First off, make sure you're running Android Studio 1.3 or higher. As long as you're keeping current with the Android Studio

Next I had to edit the project's build.gradle file, my dependencies section looks like this:

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.+'
}

After that, I updated the build.gradle for the app module. The first two line in the file are:

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

That's pretty much about it. Now that our project is aware of data binding, let's see about the code and UI changes I had to make.

Using Data Binding

From here, you might be best off reading Google's docs on data binding, just to get a feel for how things work. If you're familiar with data binding in XAML (say WPF or Xamarin.Forms), you might notice some simularities.

(Allow me digress a bit and offer this piece of advice again: think twice about binding directly to your data model. This is a perfect opportunity to bring some Model-View-ViewModel goodness into your Android application. I'm not going to talk to much about MVVM though.)

Updating the Source Code

To keep my UI as code free as possible, I abstracted much of the data binding logic into the following class (his isn't all the code, just the parts relevant for this example):

public class PhonewordViewModel extends BaseObservable {
    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;
        onTranslate(null);

    }

    public void onTranslate(View v) {
        mPhoneNumber = toNumber(mPhoneWord);

        if (TextUtils.isEmpty(mPhoneNumber)) {
            mCallButtonText = "Call";
            mIsTranslated = false;
        } else {
            mIsTranslated = true;
            mCallButtonText = "Call " + mPhoneNumber + "?";
        }
        notifyPropertyChanged(net.opgenorth.phoneword.BR.phoneNumber);
        notifyPropertyChanged(net.opgenorth.phoneword.BR.isTranslated);
        notifyPropertyChanged(net.opgenorth.phoneword.BR.callButtonText);
    }
}

Here I've encapsulated logic into a view class that subclasses BaseObservable. Subclassing isn't mandatory – a naked POJO will work too. However, BaseObservable provides the infrastructure for setting up the data binding; the POJO can notify registered listeners as values change. As well, POJO's should be kept as dumb as possible.

Notice that the getters are adorned with the @Bindable annotation - this identifies how the listeners should retrieve values from the properties.

It's the responsibility of the bound class to notify clients when a property has changed. You can see this happening with the use of notifyPropertyChanged. This causes a signal to be raised to listeners; this is how they find out the name has changed.

The BR class is generated by the data binding library. It is to data binding what the R class is to layout files. Each POJO field or method adorned with @Bindable will have a constant declared in theBR class at compile time corresponding to the name. So, getPhoneNumber() becomesBR.phoneNumber.

With the code out of the way, it's time to update the layout.

Update the XML Layout

There were a couple of changes that I needed to make to my existing layout for things to work:

  1. Declare some variables in my layout.
  2. Identify properties on the various widgets that will be bound to the variable declared above.
  3. Establish the data binding in the Activity.

Android's data binding requires that <layout> be the root element of the layout. My old layout started with a <LinearLayout>. It's also necessary to add a <data/> section that will declare variables and the classes that will be bound to.

Declare A Variable

We need to declare a variable that the data binding framework can... bind too. I had to add a <data>element with a child <variable> element that names the variable and identifies the type Android should use for the binding:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

        <data>
        <variable
            name="phonewordVM"
            type="net.opgenorth.phoneword.PhonewordViewModel" />
    </data>

    <!-- my old layout is here, but omitted for clarity -->

</layout>

This declares a variable phonewordVM that I can use inside my layout file.

Notice that the xmlns:app="http://schemas.android.com/apk/res-auto" will automatically drag local namespaces into your XML. This helps you out a bit because you don't have to explicitly declare all the namespaces in layout file.

Declare the Bindings in the Layout

Next, I need to set up the binding. In this example, all I want to do is to bind setName()/getName()in my POJO to an EditText. This little XML snippet shows the binding in action:

<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" />

Notice the syntax to declare the binding: @{phonewordVM.phoneWord}. This binds the EditText tosetPhoneWord/getPhoneWord in PhonewordViewModel. With this in place, the last thing to do is to setup the data binding in the activity.

Establish the Data Binding

Finally, setting up the data binding. This is a very minimal amount of code. We no longer have to first get a reference to a view, access properties on the view, and then manually transfer the value of that view to some domain object or variable in our application.

Below is a snippet from the fragment:

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;
    }
}

There are a couple of key things to notice here. First, observe that the fragment inflates a view calledfragment_main.xml. The data binding library generates the code of a class calledFragmentMainBinding. The name of the binding class is derived from the name of the layout file, with the work Binding appended to it.

Once the binding is instantiated, I tell it what object to bind to. The data binding library created a setter called setPhonewordVM – this is because we declared the variable phonewordVM in our layout file above.

Another interesting thing is that the code for the fragment does not use findViewById or hold a reference to any of the views layout. That is because the FragmentMainBinding has those references. So, for example, if I want to get the value of an EditText with the id+@id/phonewordText, then mBinding.phonewordText.getText() will do the trick.

I set the OnClickListener for the buttons in a very traditional way. In theory, the data binding library should allow to bind event listeners to methods on a view model. However, I have't been able to get that to work yet. Hopefully I'll have more luck next version of the data binding library (and/or an update to the docs for the data binding library)

Sie Sind Fertig

With all this, data binding has been accomplished. It may seem like a lot of code, and perhaps it is for such a trivial example. Where the true power of this comes into play is when you want to write tests for your code. Two way data binding lays the framework for the Model-View-View Model pattern, which in turn helps you create a loosely coupled app that is easier to test.



http://www.opgenorth.net/blog/2015/09/15/android-data-binding-intro/

  评论这张
 
阅读(246)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2016