How Data Binding and Observable pattern works in Android? - android

I am learning Data Binding in Android , and I am little bit confused how exactly Observable pattern works.
I have one example:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
activityMainBinding.setStudent(new Student("Rahul"));
activityMainBinding.executePendingBindings();
}
}
And
public class Student extends BaseObservable {
private String name;
public Student(String rahul) {
name = rahul;
}
#Bindable
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
}
xml style:
<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="student"
type="example.com.password.Student"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="example.com.password.MainActivity"
android:orientation="vertical">
<EditText
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:text="#={student.name}"/>
<TextView
android:id="#+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{student.name}"/>
<Button
android:id="#+id/textViewa"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
I have some questions about workflow here:
How TextView exactly knows when is Student.name changed ?
What #Binding annotation on getter method and notifyPropertyChanged(BR.name) in setter method do ?

How TextView exactly knows when is Student.name changed ?
What #Binding annotation on getter method and
notifyPropertyChanged(BR.name) in setter method do ?
For every method annotated with #Bindable annotation, a BR(Binding Resource, Data Binding equivalent of R.java) field with the method name is generated remove get prefix (here BR.name).
On calling notifyPropertyChanged(BR.name), databinding invalidates the usages of the mapped method (here getName()) and the method is called to fetch new data and the new value is updated in textView.

Related

What is the correct way to do two-way data binding on android?

i made a simple hello world for 2 way data binding and seams works perfectly (when write on editext, the textview update automatically), but all code found online like official documentation has much more code and complications like https://developer.android.com/topic/libraries/data-binding/two-way
this is my code:
public class MainActivity extends AppCompatActivity {
public String pippo;
public Boolean visible = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DataBindingUtil.setContentView(this, R.layout.activity_main);
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="pippo"
type="String" />
<variable
name="visible"
type="Boolean" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={pippo}" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{pippo}"
android:visibility="#{visible ? android.view.View.VISIBLE: android.view.View.GONE}" />
<CheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="#={visible}" />
</LinearLayout>
</layout>
In particular documentation use this things but seams useless:
BaseObservable
#Bindable
code to Avoids infinite loops
notifyPropertyChanged
so, what wrong or missing with my code?
In the two-way data binding you need to create class that extends from BaseObservable, annotate getters with #Bindable and call notifyPropertyChanged in your setters as below:
public class Person extends BaseObservable {
private String name;
Person(String name) {
this.name = name;
}
#Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
}
Then pass this class as a databinding layout variable of type Person.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="person"
type="com.example.android......Person" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={person.name}" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{person.name}" />
</LinearLayout>
</layout>
Note: change the class path in the type attribute.
and then set this layout variable in your activity with setPerson()
public class ExampleActivity extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DataBindingUtil.setContentView(this, R.layout.activity_example);
ActivityExampleBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_example);
mActivityMainBinding.setPerson(new Person(""));
}
}
The most simple way to me is using #={variable} instead #{variable}
You can see it in:
https://developer.android.com/topic/libraries/data-binding/two-way
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={viewModel.name}"
android:inputType="text" />

Fixing data binding errors in Android for linking a button with a function

I have a test project where I want to bind the press of a button to trigger a function via the DataBinding Libray and add:command.
Unfortunately, I'm getting the error:
Found data binding errors.
****/ data binding error ****msg:Could not resolve com.example.ckleineidam.testproject.ViewModel.testButton as an accessor or listener on the attribute.
MainActivity:
public class MainActivity extends AppCompatActivity {
ViewModel mModel;
ActivityMainBinding binding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mModel = new ViewModel(this);
binding.setViewModel(mModel);
}
}
ViewModel:
public class ViewModel extends BaseObservable {
private static final String TAG = "VIEW_MODEL";
private Context mActivity;
public ViewModel(Context context) {
this.mActivity=context;
}
public void testButton(){
Log.i(TAG, "Button Click");
}
}
activity_main.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"
tools:context=".MainActivity">
<data>
<variable
name="ViewModel"
type="com.example.ckleineidam.testproject.ViewModel" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="title"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/activation_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Test Button"
android:background="?android:attr/selectableItemBackground"
app:command="#{ViewModel.testButton}"
app:layout_constraintTop_toBottomOf="#id/title" />
</android.support.constraint.ConstraintLayout >
</layout>
The code is also as an example project at Github.
You are getting this error because there is no attribute app:command on a button.
If you are trying to achieve an onClick functionality, you can use android:onClick="#{ViewModel.testButton}" and change your function signature to void testButton(View view).
To use custom attributes, you need to define a binding adapter

Android two-way databinding, textview not updating

I am trying to use MVVM pattern with android databinding.
I have an edittext and a textview that should show the same text when I type in the edittext field by binding to the model via the observablefield with a LocationType model object.
From testing both fields are set to "hello" when I start the app, as expected.
But when I type in the edittextfield the textview does not update, even tough the model object gets updated correctly as can be seen with by debug.
When I use an:
observablefield<String>
drop using the model and just set to some text and update the xml to use this field. It works as intended.
model:
public class LocationType {
private String locationType;
public String getLocationType() {
return locationType;
}
public void setLocationType(String locationType) {
this.locationType = locationType;
}
}
modelView:
public class LocationTypeViewModel extends BaseObservable {
#Bindable
private ObservableField<LocationType> locationTypeObservableField = new ObservableField<>();
private Context context;
LocationType locationType;
public LocationTypeViewModel(Context context) {
this.context = context;
locationType = new LocationType();
locationType.setLocationType("Hello");
locationTypeObservableField.set(locationType);
}
public ObservableField<LocationType> getLocationTypeObservableField() {
Log.d("CALLED GET", locationType.getLocationType());
return locationTypeObservableField;
}
}
XML:
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<variable
name="viewModel"
type="fragment.LocationType.viewmodel.LocationTypeViewModel"/>
</data>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="#{viewModel.locationTypeObservableField.locationType}"
android:id="#+id/edittext1"/>
<TextView
android:layout_below="#+id/edittext1"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="#{viewModel.locationTypeObservableField.locationType}"
android:id="#+id/text"
/>
</RelativeLayout>
</layout>
android:text="#={viewModel.locationTypeObservableField.locationType}"
You forgot to add "=" .check and compare above line with your code.
Two way databinding should be like this
android:text="#={ProfileViewModel.mobileNumber}"
You forgot to add "="
The problem is you are observing changes to the field inside LocationTypeViewModel which points to a LocationType object. That will only notify changes when the object reference changes. What you want is an observer on the actual String field.
You are also missing an important two-binding symbol on your EditText. You want to use #={obj.fieldName} instead of #{obj.fieldName}. The #= signals that you want changes to propagate back to the source.
Here is your code edited to work as intended:
public class LocationType {
private String location;
public LocationType(String location) {
this.location = location;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
public class LocationTypeViewModel extends BaseObservable {
private final LocationType locationType;
public LocationTypeViewModel() {
locationType = new LocationType("Hello");
}
#Bindable
public String getLocation() {
return locationType.getLocation();
}
public void setLocation(String location) {
locationType.setLocation(location);
notifyPropertyChanged(BR.location);
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<variable
name="viewModel"
type=".LocationTypeViewModel"/>
</data>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="#={viewModel.location}"
android:id="#+id/edittext1"/>
<TextView
android:layout_below="#+id/edittext1"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="#{viewModel.location}"
android:id="#+id/text"
/>
</RelativeLayout>
</layout>

MVVM : How to Concat the String in model class?

I created app using retrofit callback. In that i wanna show some information with text. In textView i already binded the data, also i need to concat some texts along with that.
My Code Follows
View:
<?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="UserProfile"
type="com.practical_session.sai.instaprouser.Profile.model.UserProfileInfo" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/linear"
android:orientation="vertical"
android:background="#drawable/animation_list"
tools:context="com.practical_session.sai.instaprouser.Profile.view.ProfileActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.AppCompatTextView
android:id="#+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/imageView"
android:layout_marginLeft="10dp"
android:layout_marginTop="8dp"
android:textSize="20dp"
android:text="#{UserProfile.username}"
android:textColor="#android:color/black" />
</RelativeLayout>
</LinearLayout>
</layout>
Model:
public class UserProfileInfo extends BaseObservable {
#SerializedName("username")
#Expose
private String username;
#Bindable
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
Expected Output:
Model Return + "Concat String"
Try this Simple Way
All your processing things and Business Logic should be in Controller Class (i.e: ViewModel in MVVM)
write one method in Controller, pass the value to Controller, then that method returns the value with Concat String
Code Sample
android:text="#{Controller.getUserProfile(UserProfile.username)}"
Controller Method
public String getUserProfile(String name)
{
return name + "concat string";
}
concat it in xml
android:text="#{UserProfile.username + `Concat String`}"

How to update an object from the UI with Android Data Binding?

I am using Data Binding and I've created a very simple class
public class ViewUser extends BaseObservable {
private String name;
#Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
}
with a simple layout
<?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="user"
type="com.example.ViewUser" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:lines="3"
android:text="#{user.name}" />
</LinearLayout>
</layout>
When I update the object, the UI updates without any problem, but if I change the value of the EditText from the UI and then get the user using the DataBindingUtil .getUser(), it doesn't have the updated value. Is it possible to have the property updated automatically or do I have to update the object using some event like TextWatcher's onTextChanged?
Your xml tag android:text is missing a =, after the #:
android:text="#={user.name}"
The #={} means "two way data binding". The #{} is one way data binding.
For set and get Updated model from your XML. you have to do this in Edittext:
from:
android:text="#{user.name}"
to :
android:text="#={user.name}"
Happy coding!!

Categories

Resources