Faster Data Binding in Android

Faster Image Loading With DataBinding Using RecyclerView in Android.

 

This post describes the usage of data binding in Android applications. Data binding allows to synchronize your user interface with your application model and logic.

- What is Data Binding ?


Android Data Binding creates a link between UI layer and the underlying data model that holds the information to display. In normal Android app, it is necessary to find the view and update the content. Every time data changes the User Interface widget (For example:TextView, ImageView used in below example.) bound to it is needs to be update. It eliminates the need for these method calls “findViewById” and “setText.”

- Getting Ready from Gradle Configuration Set Up

To configure your app to use data binding, add the dataBinding element to your build.gradle file in the app module.

android {
    ....
    dataBinding {
        enabled = true
    }
}
 

- Creating Project 

Here I have created a Android Studio project with package com.kotlincoders.ravi.logicpractices also Activity as MainActivity and layout as activity_main . 

- Creating Model Class

Lets create a POJO class which have fields result, type and imageUrl. Generate getters and setters. Our model class extends BaseObservable. This BaseObservable implements Observable interface. By extending from BaseObservable class it is easy to listen for update when the value in the fields change. In this example I need to listen for changes in result field. So I add @Bindable annotation to the getter for result. And in the setter setImageUrl I call notifyPropertyChanged(BR.obj). Where BR is auto generated class and obj is an object of Model class.


GetSetClass.Java

package com.kotlincoders.ravi.logicpractices;

import android.content.Context;
import android.databinding.BaseObservable;
import android.databinding.Bindable;

/** * Created by ravi on 2/23/18. */
public class GetSetClass extends BaseObservable {

    String result;
    int number;
    String imageUrl;
    String type;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public GetSetClass(Context ctx) {
    }

    public String getImageUrl() {

        return imageUrl;
    }

    public void setImageUrl(String imageUrl) {
        this.imageUrl = imageUrl;
        notifyPropertyChanged(BR.obj);

    }

    public GetSetClass(String result, int number) {
        this.result = result;
        this.number = number;
    }

    public GetSetClass(String type, int number, String imageUrl) {
        this.type = type;
        this.number = number;
        this.imageUrl = imageUrl;
    }

    @Bindable    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}



-Creating Layout

All layout files that intend to use data binding techniques must have a layout<> root tag.
To bind objects it is required to add data<> tag within the layout tag, before the UI view root. data<> element can have multiple variable<> tag within it that describes a property that can be used within the layout.

For binding data the format should be similar to @ followed by field name in braces { }. For example see in TextView.

<?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>

        <variable 
 name="logicvar" 
 type="com.kotlincoders.ravi.logicpractices.GetSetClass" />

        <variable 
 name="activity" 
 type="com.kotlincoders.ravi.logicpractices.MainActivity" />

    </data>


    <RelativeLayout 
 android:layout_width="match_parent" 
 android:layout_height="match_parent">

        <LinearLayout 
 android:id="@+id/liner2" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" 
 android:orientation="vertical">


            <EditText 
 android:id="@+id/editText" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 android:layout_gravity="center_horizontal" 
 android:layout_marginBottom="20dp" 
 android:layout_marginTop="30dp" 
 android:ems="10" 
 android:hint="Enter number:" 
 android:inputType="number" />

            <Button 
 android:id="@+id/button" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 android:layout_gravity="center_horizontal" 
 android:background="@color/colorAccent" 
 android:gravity="center" 
 android:onClick="@{()->activity.logicFun(logicvar.type)}" 
 android:text="Button" 
 android:textColor="#fff" />

            <TextView 
 android:id="@+id/textView" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 android:layout_gravity="center_horizontal" 
 android:layout_marginTop="30dp" 
 android:text="@{logicvar.result}" />

        </LinearLayout>


    </RelativeLayout>


</layout>
 


where GetSetClass refers to the model and result is the String field in that model. When this result field in the model is updated it will be reflected in the TextView. Similarly for EditText take this line,

android:text="@{logicvar.result}" 
 
And in the Button take this line,
 
android:onClick="@{()->activity.logicFun(logicvar.type)}"
 
where activity variable denotes our MainActivity. The logicFun() is a method in our Activity which takes String as parameter. When the button is pressed the type String is passed from the model and the method is called. The -> operator shows this is a lambda expression.

-Creating Activity  
Here we create a ActivityMainBinding object which is auto generated according to the layout name.
Since the layout name is activity_main it is ActivityMainBinding.It is initialized as,

ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
Then we create a new GetSetClass object. This model is then tied to the view using the activityMainBinding.setLogicvar() method.
Similarly the Actvity using setActivity() method.
 

package com.kotlincoders.ravi.logicpractices;

import android.content.Intent;
import android.databinding.BindingAdapter;
import android.databinding.DataBindingUtil;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

import com.bumptech.glide.Glide;
import com.kotlincoders.ravi.logicpractices.databinding.ActivityMainBinding;

import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding activityMainBinding;
    private GetSetClass getSetClass;



    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);


        getSetClass = new GetSetClass(this);

        activityMainBinding.setLogicvar(getSetClass);
        activityMainBinding.setActivity(this);

        String type = getIntent().getStringExtra("Type");

        if (type == null) {
            type = "Palindrome";
        }

        getSetClass.setType(type);
        getSetClass.setResult(type);
        getSetClass.notifyChange();

        ActionBar abar = getSupportActionBar();
        abar.setTitle(type);

    }

    public void logicFun(String logicType) {
         int originalnumber;
         int remainder = 0;
         int reversenumber = 0;

        int number = Integer.parseInt(String.valueOf(activityMainBinding.editText.getText()));

        Log.e("MainActivity", "logicType====>" + logicType);


        if (logicType.equalsIgnoreCase("Palindrome")) {

            originalnumber = number;

            for (; number != 0; number = number / 10) {
                remainder = number % 10;
                reversenumber = (reversenumber * 10) + remainder;
            }

            if (reversenumber == originalnumber) {

                Log.e("MainActivity", logicType + " Number " + originalnumber);

                getSetClass.setResult(logicType + " Number " + originalnumber);
                getSetClass.notifyChange();

            } else {

                Log.e("MainActivity", "Not " + logicType + " Number " + originalnumber);
                getSetClass.setResult("Not " + logicType + " Number " + originalnumber);
                getSetClass.notifyChange();
            }

        } else if (logicType.equalsIgnoreCase("FabbinociSeries")) {

            int t1 = 0, t2 = 1, nextterm = 0;

            for (int i = 0; i <= number; i++) {
                nextterm = t1 + t2;
                t1 = t2;
                t2 = nextterm;
            }

            getSetClass.setResult("FabbinociSeries Result==>" + nextterm);
            getSetClass.notifyChange();

            Log.e("MainActivity", "Fabbinoci" + nextterm);

        } else if (logicType.equalsIgnoreCase("PrimeNumber")) {

            boolean flag = false;

            int m = number / 2;

            for (int i = 2; i < m; i++) {

                if (number % 2 == 0) {

                    getSetClass.setResult("It is Not Prime==>");
                    flag = true;

                    break;
                }
            }
            if (!flag) {
                getSetClass.setResult("It's Prime==>");
            }

            getSetClass.notifyChange();

            Log.e("MainActivity", "Prime");

        } else if (logicType.equalsIgnoreCase("Factorials")) {

            int factorials = 1;

            for (int i = 1; i < number; i++) {

                factorials = factorials * i;
            }
            getSetClass.setResult("Factorials Result==>" + factorials);
            getSetClass.notifyChange();

            Log.e("MainActivity", "Factorials");

        } else if (logicType.equalsIgnoreCase("Armstrong")) {

            int result = 0, temp = number, remainders;

            while (number > 0) {

                remainders = number % 10;
                result = result + (remainders + remainders + remainders);
                number = number / 10;

            }

            if (temp == result) {

                getSetClass.setResult("it is real Armstrong ");


            } else {

                getSetClass.setResult("it is not Armstrong ");
            }

            getSetClass.notifyChange();

        } else if (logicType.equalsIgnoreCase("PrintStars")) {

            int numROW = number;
            int rowCount = number;
            String temp = "";

            for (int i = 0; i <= numROW; i++) {

                String t = "";

                for (int j = 0; j <= i; j++) {

                    System.out.print("* ");

                    t = t + "*";

                }

                temp = temp + t + "\n";

                System.out.println();

            }

            getSetClass.setResult(temp);
            getSetClass.notifyChange();


        }

    }


} 
 

- BindingAdapter

Sometimes we want to do something more complex than simply calling a setter on the View.  A very common example is loading images off the UI thread.
In order to do it you need to create a public static method accepting as input View of the necessary type and value we specify in the layout. The method itself should have @BindingAdapter annotation and in its body you should specify string with the name of the attribute.

<ImageView 
 android:id="@+id/imageview" 
 android:layout_width="120dp" 
 android:layout_height="90dp" 
 android:padding="4dp" 
 android:src="@{obj.imageUrl}" />
 
Now,Data binding system would look for use default “android:src” attribute in Activity.
 
 @BindingAdapter("android:src")
public static void setImageUrl(ImageView view, String url) {

    Glide.with(view.getContext()).load(url).into(view);

} 
 As you can see now we can do whatever we want in that code. We can now load off the UI thread,
 just like we want to.
 
 

- Now Using data binding for RecyclerView

In this exercise you learn how to use data binding for a recyclerview. Continue to use the com.kotlincoders.ravi.logicpractices package. 

Create a new activity called RecyclerViewActivity . Ensure that you add it to your Android manifest. 
package com.kotlincoders.ravi.logicpractices;

import android.content.Intent;
import android.databinding.BindingAdapter;
import android.databinding.DataBindingUtil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;

import com.bumptech.glide.Glide;
import com.kotlincoders.ravi.logicpractices.databinding.ActivityRecyclerViewBinding;

import java.util.Arrays;
import java.util.List;

public class RecyclerViewActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private RecyclerView.Adapter mAdapter;
    private RecyclerView.LayoutManager layoutManager;
    private ActivityRecyclerViewBinding activityRecyclerViewBinding;
    private List<GetSetClass> items;

    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activityRecyclerViewBinding = DataBindingUtil.setContentView(this, R.layout.activity_recycler_view);

        activityRecyclerViewBinding.setLogicData(new GetSetClass(RecyclerViewActivity.this));
        activityRecyclerViewBinding.setActivity(this);

        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        recyclerView.setHasFixedSize(true);
        layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);

        items = Arrays.asList(
                new GetSetClass("Palindrome", 0, "http://lorempixel.com/400/400/"),
                new GetSetClass("FabbinociSeries", 0, "http://lorempixel.com/500/400/"),
                new GetSetClass("PrimeNumber", 0, "http://lorempixel.com/300/500/"),
                new GetSetClass("Factorials", 0, "http://lorempixel.com/600/400/"),
                new GetSetClass("Armstrong", 0, "http://lorempixel.com/700/500/"),
                new GetSetClass("PrintStars", 0, "http://lorempixel.com/400/500/")
         );

        // define an adapter        mAdapter = new ReceyclerViewAdapter(items);
        recyclerView.setAdapter(mAdapter);

        recyclerView.addOnItemTouchListener(
                new RecyclerItemClickListener(this, recyclerView, new RecyclerItemClickListener.OnItemClickListener() {
                    @Override                    public void onItemClick(View view, int position) {
                        // do whatever
                        GetSetClass getSetClass = items.get(position);
                        startActivity(new Intent(RecyclerViewActivity.this, MainActivity.class)
                        .putExtra("Type",getSetClass.getType()));

                    }

                    @Override                    public void onLongItemClick(View view, int position) {
                        // do whatever                    }
                })
        );

    }

    @BindingAdapter("android:src")
    public static void setImageUrl(ImageView view, String url) {

        Glide.with(view.getContext()).load(url).into(view);

    }
}

- Adjust layout file and activity to use RecyclerView with data binding.

- Create the following layout called activity_recycler_view.xml

<?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" 
 xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable 
 name="logicData" 
 type="com.kotlincoders.ravi.logicpractices.GetSetClass" />

        <variable 
 name="activity" 
 type="com.kotlincoders.ravi.logicpractices.RecyclerViewActivity" />

    </data>

    <RelativeLayout 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" 
 tools:context="com.kotlincoders.ravi.logicpractices.RecyclerViewActivity">

        <android.support.v7.widget.RecyclerView 
 android:id="@+id/recyclerView" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" />

    </RelativeLayout>
 </layout>
 


- Create the following layout called row_item_layout.xml.


<?xml version="1.0" encoding="utf-8"?>
<layout
 xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable 
 name="obj" 
 type="com.kotlincoders.ravi.logicpractices.GetSetClass" />
 </data>

    <LinearLayout 
 android:layout_width="match_parent"
 android:layout_height="wrap_content" 
 android:orientation="vertical">

    <android.support.v7.widget.CardView 
 android:id="@+id/card" 
 android:layout_width="match_parent" 
 android:layout_height="wrap_content" 
 android:padding="10dp">


        <LinearLayout 
 android:layout_width="match_parent" 
 android:layout_height="wrap_content" 
 android:orientation="horizontal">

            <ImageView 
 android:id="@+id/imageview" 
 android:layout_width="120dp" 
 android:layout_height="90dp" 
 android:padding="4dp" 
 android:src="@{obj.imageUrl}" />

            <TextView 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 android:layout_gravity="center" 
 android:layout_marginLeft="20dp" 
 android:gravity="center" 
 android:text="@{obj.type}" 
 android:textAllCaps="true" 
 android:textAppearance="@style/Base.TextAppearance.AppCompat.Small" 
 android:textColor="#000000"                 />

        </LinearLayout>
    </android.support.v7.widget.CardView>

    </LinearLayout>

</layout>
 
 
- Create the following adapter.




package com.kotlincoders.ravi.logicpractices;

import android.database.DatabaseUtils;
import android.databinding.DataBindingUtil;
import android.databinding.ViewDataBinding;
import android.databinding.generated.callback.OnClickListener;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

/** * Created by ravi on 2/26/18. */
public class ReceyclerViewAdapter extends RecyclerView.Adapter<ReceyclerViewAdapter.MyViewHolder> {

    List<GetSetClass> list;
    public ReceyclerViewAdapter(List<GetSetClass> list) {

        this.list = list;

    }

    @Override    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());

        ViewDataBinding viewDataBinding = DataBindingUtil.inflate(layoutInflater,
                R.layout.row_item_layout, parent, true);



        return new MyViewHolder(viewDataBinding);
    }

    @Override    public void onBindViewHolder(MyViewHolder holder, int position) {

        GetSetClass getSetClass = list.get(position);

        holder.bind(getSetClass);
    }

    @Override    public int getItemCount() {

        return list.size();
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {

        ViewDataBinding binding;

        public MyViewHolder(ViewDataBinding itemView) {

            super(itemView.getRoot());
            this.binding = itemView;
        }

        public void bind(Object object) {

            binding.setVariable(BR.obj, object);
            binding.executePendingBindings();

        }
    }
}

- Validate your application

Start your application and Change LAUNCHER in AndroidManifest.xml
navigate to your RecyclerViewActivity activity. Ensure the list is correctly displayed.

Add the following dependency to your app/build.gradle file.

compile 'com.github.bumptech.glide:glide:3.8.0'

compile 'com.android.support:recyclerview-v7:25.1.1'

implementation 'com.android.support:cardview-v7:27.1.0'


To allow Glide to download from the Internet, add the permission to use the Internet to your manifest.

<uses-permission android:name="android.permission.INTERNET" />

-ScreenShots 


- Complete Project Files

You can download the complete project as zip or fork from our Github repository.

https://github.com/AndroidKotlinCoders/LogicPractices


Comments

Popular posts from this blog

Using Kotlin JSON Parsing with Listview

The Android Network connection Operations

Android GPS Location tracking User's current location.