--

Thursday 8 November 2018

Chapter -2 : Modern Android Architecture ‘Android Jetpack’ - Working with DataBinding


       The Data Binding Library i.e. support library enables us to bind UI components in our layouts to data sources in your app using a declarative format rather than programmatically. In Simple words, Data binding enables a way to combine the UI with business logic allowing the UI values to update automatically without manual intervention.

Benefits

  • Reduces lot of boilerplate code in our business logic while sync up the UI when new data is available. 
  • Making them simpler and easier to maintain. 
  • Improve the app's performance 
  • Help prevent memory leaks and null pointer exceptions.


How to use ?

To get started with DataBinding, open the build.gradle located under app and enable dataBinding under android module. Once enabled, Sync the project



Adding Data Binding element to Layout file


The expression language allows us to write expressions that connect variables to the views in the layout. The next step in converting the layout file to a data binding layout file is to add the data element. To enable DataBinding in a layout, the root element should start with <layout> tag. Along with it, <data> and <variable> tags are used.



<layout> – Indicate the compiler that this layout has data binding in it so generate the binding class for this layout.
<import> – Similar to how we interpret imports in normal classes. Import a class in this layout.
<variable> – similar to how we interpret variables in normal classes. Declare a variable 'name' and it’s type for the layout to use.
<data> – Parent tag for <import> and <variable> tags for the “data” that we are going to bind for this layout.


Access data in layout through Layout Expressions




It’s structured like a normal class where our imports and most of our variables are declared at the top and we use them at the bottom. The user variable declared within data describes a property that may be used within this layout. Also, if you want to use expressions inside your layout, you can call attribute properties using the “@{}" syntax.


Project structure for Binding Data




The Data Binding Library provides classes and methods to easily observe data for changes. You don't have to worry about refreshing the UI when the underlying data source changes. You can make your variables or their properties observable. The library allows you to make objects, fields, or collections observable.

public class User extends BaseObservable {
String username;
String email;
String userImage;
String info;
public ObservableField<Long> numberOfFriends = new ObservableField<>();
public ObservableField<Long> numberOfArticles = new ObservableField<>();
public ObservableField<Long> numberOfFavourite = new ObservableField<>();
public User() {
}
@Bindable
public String getUsername() {
return username; 
}
public void setUsername(String username) {
this.username = username;
notifyPropertyChanged(BR.username);
}
@BindingAdapter({"profileImage"})
public static void loadImage(ImageView view, String imageUrl) {
Glide.with(view.getContext())
.load(imageUrl)
.apply(RequestOptions.circleCropTransform())
.into(view);
}
@Bindable
public String getUserImage() {
return userImage;
}
public void setUserImage(String userImage) {
this.userImage = userImage;
notifyPropertyChanged(BR.userImage);
}
@Bindable
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
notifyPropertyChanged(BR.info);
}
public ObservableField<Long> getNumberOfFriends() {
return numberOfFriends;
}
public ObservableField<Long> getNumberOfArticles() {
return numberOfArticles;
}
public ObservableField<Long> getNumberOfFavourite() {
return numberOfFavourite;
}
}
  • To make User pojo class Observable, extend the class from BaseObservable. For demonstration, both Observable and ObservableField are used in the same class.
  • For variables username, email, userImage and info., @Bindable annotation is used and notifyPropertyChanged is called upon setting new data if needed.
  • Variables numberOfArticless, numberOfFriends, numberOfFavourites are declared as ObservableFields.
  • @BindingAdapter is used to bind profileImage to ImageView in order to load the image from URL using Glide library.


Generating binding class

When you set the root tag of your layout to <layout>, a binding class is generated by the Data Binding library. For instance, activity_main.xml will have a binding class called ActivityMainBinding.
Note : This class holds all the bindings from the layout properties to the layout’s views and knows how to assign values for the binding expressions.



If you are using data binding items in a Fragment, ListView, or RecyclerView adapter, use the inflate() methods of the binding classes or the DataBindingUtil class:




In this chapter, we will make a very simple app that loads images in RecyclerView using data binding library.

DataBinding in RecyclerView


Binding a RecyclerView layout is similar to normal binding except few changes in onCreateViewHolder and onBindViewHolder methods. Create layout named article_row_item.xml. This layout contains an ImageView to render the image in RecyclerView. In this layout, data binding is enabled by keeping the root element as <layout>. The Post model in bound to this layout using <variable> tag.



Generating adapter binding class


Create a class named ArticleAdapter.java under view package. As the layout name is article_row_item.xml, the generated binding class will be ArticleRowItemBinding. In onCreateViewHolder() method, article_row_item layout is inflated with the help of ArticleRowItemBinding class. articleViewHolder.rowItemBinding.setArticle() binds the Post model to each row.

public class ArticleAdapter extends RecyclerView.Adapter<ArticleAdapter.ArticleViewHolder> {
private List<Article> articleList;
private LayoutInflater layoutInflater;
private ArticlesAdapterListener listener;
public ArticleAdapter(List<Article> articleList, ArticlesAdapterListener listener) {
this.articleList = articleList;
this.listener = listener;
}
@NonNull
@Override
public ArticleViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
if (null == layoutInflater) {
layoutInflater = LayoutInflater.from(viewGroup.getContext());
}
ArticleRowItemBinding articleRowItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.article_row_item, viewGroup, false);
return new ArticleViewHolder(articleRowItemBinding);
}
@Override
public void onBindViewHolder(@NonNull ArticleViewHolder articleViewHolder, final int i) {
articleViewHolder.rowItemBinding.setArticle(articleList.get(i));
articleViewHolder.rowItemBinding.thumbnail.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (listener != null) {
listener.onArticleClicked(articleList.get(i));
}
}
});
}
@Override
public int getItemCount() {
return articleList.size();
}
public class ArticleViewHolder extends RecyclerView.ViewHolder {
private ArticleRowItemBinding rowItemBinding;
public ArticleViewHolder(final ArticleRowItemBinding binding) {
super(binding.getRoot());
this.rowItemBinding = binding;
}
}
public interface ArticlesAdapterListener {
void onArticleClicked(Article post);
}
}


Loading RecyclerView in MainActivity


As the main activity layout name is activity_main, the generated binding class will be ActivityMainBinding. loadUserData() renders the user information such as username, , articles, friends and favourite count. init() initialize the RecyclerView with sample images data. AppClickHandlers handles the click events of UI elements. Here, all the binding of click events is done via xml layout only. We don’t explicitly assign anything from activity code.

public class MainActivity extends AppCompatActivity implements ArticleAdapter.ArticlesAdapterListener {
private User user;
private ActivityMainBinding binding;
private AppClickHandlers handler;
private RecyclerView recyclerView;
private ArticleAdapter articleAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
handler = new AppClickHandlers(this);
loadUserData();
init();
}

private void loadUserData() {
user = new User();
user.setUsername("Mr. Sherlock Holmes");
user.setUserImage("https://tse4.mm.bing.net/th?id=OIP.gHhJlg-RAvR-XosdERsIMQHaEo&pid=Api&w=1440&h=900&rs=1&p=0");
// updating ObservableField
user.numberOfArticles.set(1500L);
user.numberOfFriends.set(205090L);
user.numberOfFavourite.set(10L);
// display user
binding.setUser(user);
// assign click handlers
binding.setHandlers(handler);
}
private void init() {
recyclerView = binding.recyclerView;
recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
recyclerView.addItemDecoration(new GridItemDecoration(3, ImageUtil.dpToPx(MainActivity.this, 4), true));
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setNestedScrollingEnabled(false);
articleAdapter = new ArticleAdapter(getArticles(), this);
recyclerView.setAdapter(articleAdapter);
}
private ArrayList<Article> getArticles() {
ArrayList<Article> articles = new ArrayList<>();
for (int i = 1; i < 10; i++) {
Article article = new Article();
article.setImageUrl("https://tse3.mm.bing.net/th?id=OIP.f1bDVK_DN6xiTaYAM79ucgHaEK&pid=Api");
articles.add(article);
}
return articles;
}

@Override
public void onArticleClicked(Article article) {
Toast.makeText(getApplicationContext(), "Article clicked! " + article.getImageUrl(), Toast.LENGTH_SHORT).show();
}

public class AppClickHandlers {
Context context;
public AppClickHandlers(Context context) {
this.context = context;
}
public void onFriendsClicked(View view) {
Toast.makeText(context, "Friends is clicked!", Toast.LENGTH_SHORT).show();
}
public void onFavouriteClicked(View view) {
Toast.makeText(context, "Favourite is clicked!", Toast.LENGTH_SHORT).show();
}
public void onArticlesClicked(View view) {
Toast.makeText(context, "Article is clicked!", Toast.LENGTH_SHORT).show();
}
}
}


Run and test the app once. Make sure the device is having internet connection as images will be downloaded from network.
I have uploaded the latest source code in GitHub for your reference. Kindly raise your queries in the command section.

References :

https://developer.android.com/topic/libraries/data-binding/
https://www.androidhive.info/android-working-with-databinding/
http://imakeanapp.com/android-jetpack-data-binding/

Happy coding!!!
Cheers!!!