Anyone who has dabbled a bit in Android Development will know that Android is lacking a key component of MVVM: The ability to bind data without a couple of lines of code.
For instance, setting a TextView to some value retrieved from a server is pretty complex, considering the simplicity of the task:
TextView textView = (TextView)v.findViewById(R.id.text_view); if (TextUtils.isEmpty(mText)){ textView.setText(mText); textView.setVisibility(View.VISIBLE); } else { textView.setVisibility(View.INVISIBLE); }
Doing everything this way can become a bit cumbersome and leads to very cluttered code. I don’t think anyone needs much convincing that this process of manually binding data is not ideal.
Cue: Android Data Binding Library (It’s like someone heard our cries? :-)). The 6 lines above can be converted into 2:
android:visibility="@{TextUtils.isEmpty(page.text) ? View.GONE : View.VISIBLE}" android:text="@{page.htmlText}"
I decided to take a look and try it out, and see how well it would work for my applications.
Initial Impressions:
- Very powerful library
- Less cluttered code
- Less view logic in Activity and Fragment classes (yay!?!)
- Compatible from API version 7+ (2.1)
- Not recommended for use in production yet (It’s currently in Public Beta Phase but you are welcome to ship your app using it, it’s just not advised)
- No support in Android Studio for syntax highlighting or code completion
How to get started with the Android Data Binding Library:
- To get started with the Data Binding Framework, you need to include a couple of Gradle Dependencies in your top level build.gradle file in the dependencies section:
classpath "com.android.tools.build:gradle:1.3.0" classpath "com.android.databinding:dataBinder:1.0-rc1"
- In the Top Level build.gradle make sure the following is defined (It probably is already):
allprojects { repositories { jcenter() } }
- In each module of your project make sure to include the following right after the android plugin is applied:
apply plugin: 'com.android.application' apply plugin: 'com.android.databinding'
- Create your object that you wish to represent in your view. In our case we have a Page object which contains a number, image link and some text.
public class Page { private Integer pageNumber; private String image; private String text; public Integer getPageNumber() { return pageNumber; } public void setPageNumber(Integer pageNumber) { this.pageNumber = pageNumber; } public String getImage() { return image; } public void setImage(String image) { this.image = image; } public String getText() { return text; } public void setText(String text) { this.text = text; } public Page() { } public Spanned getHtmlText() { if (TextUtils.isEmpty(getText())){ return SpannedString.valueOf(""); } return Html.fromHtml(getText()); } }
- Create Layout XML as per usual. We have a ImageView and a TextView in our layout. At the start of the layout we will define what objects and methods will be used in this page.
<?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"> <data> <import type="android.view.View"/> <import type="android.text.TextUtils"/> <variable name="page" type="org.company.pojo.Page"/> </data> <LinearLayout android:layout_width="50dp" android:layout_height="match_parent" android:visibility="@{page == null ? View.INVISIBLE : View.VISIBLE}" android:orientation="vertical"> <ImageView android:id="@+id/image_view_page" android:layout_width="match_parent" android:layout_height="50dp" android:visibility="@{TextUtils.isEmpty(page.image) ? View.GONE : View.VISIBLE}" app:imageUrl="@{page.image}"/> <TextView android:id="@+id/text_view_page" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/image_view_page" android:text="@{page.htmlText}" android:visibility="@{TextUtils.isEmpty(page.text) ? View.GONE : View.VISIBLE}"/> </LinearLayout> ... </layout>
Let’s go through the new changes in this XML:
- As you can see we no longer use a LinearLayout as the start tag, instead we use <layout> with the namespace declarations.
- There is now a <data> section at the top of the file, which will include your imports to the different libraries you require. In this case, I required the TextUtils class. You can access any static methods define in a class. In this example I use the method TextUtils.isEmpty() to do different view logic.
- <variable> tag defined, this allows you to define objects within the XML that will be used on the View itself.
- Accessing a variable and some of its properties is simple:
android:text="@{page2.htmlText}" android:visibility="@{TextUtils.isEmpty(page2.text) ? View.GONE : View.VISIBLE}"
The words @{page2.htmlText} will run the method getHtmlText() on the page object. One thing to note: Android Studio looks as if the method getHtmlText() is unused, since it hasn’t found the usage in the XML but after deleting it, the project will fail to compile.
- After creating the Page object and the XML that will use the object, how do we go about putting the two together? Android will generate the object FragmentPageBinding (this name will change depending on your layout, eg. activity_book.xml will create the object ActivityBookBinding ) which contains all the objects that you define in the <data> section of your corresponding XML file. From then, you just set the variables and your view will contain the correct values. As you can see, no view logic is sitting in my Fragment or Activity. Yay!
@Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { FragmentPageBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_page, container, false); binding.setPage(page); return binding.getRoot(); }
- In the above code, you may also notice that on the ImageView we have the attribute app:imageUrl=”@{page.image}” . I like to load images from the web, in a background thread – using a library like Glide or Picasso . Create a Utils class called BindingUtils and pop this method in there. Now your ImageView will call this section of code with the url that we passed to it. We can then do what we like with it. In this case, we load the image into the view.
@BindingAdapter({"bind:imageUrl"}) public static void loadImage(ImageView view, String url) { Glide.with(view.getContext()).load(url).into(view); }
In Part 2 we will look at Two Way Binding and Binding Adapters.
Leave a Reply
You must be logged in to post a comment.