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.
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.
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
https://github.com/AndroidKotlinCoders/LogicPractices
Comments
Post a Comment