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

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

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" />

How Data Binding and Observable pattern works in 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.

Android data binding not shown

I am trying to use data binding in my app, but nothing is shown.
This is what I did:
I created an New Project with an Empty Activity.
I added
dataBinding {
enabled = true
}
to my build.gradle
This is my activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="game" type="my.name.bar.Bar"/>
</data>
<android.support.constraint.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="my.name.bar.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{game.GetFoo()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</layout>
And this is my Bar class
public class Bar {
private String str;
public Bar() {
str = "Foo";
}
public String GetFoo() {
return str;
}
}
But I am only getting a white screen. If I replace the binding with just "Hello World!" then everything works fine. What am I doing wrong?
(promoted from the comment)
You must bind the value. In your case, it is something like this:
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setGame(new Bar());

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`}"

ViewDataBinding getVariable?

The 2 main questions are :
Why ViewDataBinding doesn't have a method like getVariable("variableName") that will look up for a variable and returns it or null if no variable with this name exists.
Is their any way/workaround to achieve this kind of behavior?
So to be more explicit : if I don't know the type of my ViewDataBinding, is their a way to get its variable or I must know its type?
Here is how it is working actually :
I havea layout called my_layout.xml :
<layout>
<data>
<variable
android:name="myVaribale"
android:type="String"/>
</variable>
</data>
<RelativeLayout
android:id="#+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="#{myVariable}"/>
</RelativeLayout>
<layout>
I inflate an instance of its ViewDataBinding :
MyLayoutBinding binding = (MyLayoutBinding) DataBindingUtil.inflate(inflater, R.layout.my_layout, root, false);
I can get its variable by calling the appropriate method :
String myVariable = binding.getMyVariable();
So this was how to get a variable when we know the type of the ViewDataBinding.
My problem is here :
Let's imagine I have 3 layouts called my_layout_1, my_layout_2, my_layout_3 and those 3 layouts are surrounded by a <layout> tag.
Those 3 layouts has also the same variable (myVariable).
So here are the layouts :
my_layout_1
<layout>
<data>
<variable
android:name="myVariable"
android:type="String"/>
</variable>
</data>
<RelativeLayout
android:id="#+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="#{myVariable}"
style="#style/my_style_1"/>
</RelativeLayout>
<layout>
my_layout_2
<layout>
<data>
<variable
android:name="myVariable"
android:type="String"/>
</variable>
</data>
<RelativeLayout
android:id="#+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="#{myVariable}"
style="#style/my_style_2"/>
</RelativeLayout>
<layout>
my_layout_3
<layout>
<data>
<variable
android:name="myVariable"
android:type="String"/>
</variable>
</data>
<RelativeLayout
android:id="#+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="#{myVariable}"
style="#style/my_style_3"/>
</RelativeLayout>
<layout>
So the different layout represent a text with a different style (I know it can be achieved by others way but the goal is just to explain why I need this function getVariable(variableName).
Imagine now that I got a layout called my_text_container which will randomly contain one of the 3 layouts.
my_text_container
<layout>
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<layout>
So I'll add a text by doing something like this (again, it's just for demonstration purpose) :
MyTextContainerBinding textContainerBinding = (MyTextContainerBinding) DataBindingUtil.inflate(inflater, R.layout.my_text_container, root, false);
int textLayoutRes;
int randomNumber = generateNumberBetween0and2();
switch(randomNumber) {
case 0: textLayoutRes = R.layout.my_layout_1;
break;
case 1: textLayoutRes = R.layout.my_layout_2;
break;
case 2: textLayoutRes = R.layout.my_layout_3;
break;
}
textContainerBinding.getRoot().addView(DataBindingUtil.inflate(inflater, textLayoutRes, root, false));
And finally I want to retrieve myVariable but I don't know if it's a layout 1, 2 or 3. Basically what could be done now is to use instanceof operator to check whether its a type MyLayoutBinding1, MyLayoutBinding2 or MylayoutBinding3. But as I said this use-case is only for demonstration purpose and I could have 999 different layouts.
So what I would like to do is :
ViewDataBinding myLayout = DataBindingUtil.getBinding(textContainerBinding.getRoot().getChildAt(0));
String myVariable = myLayout.getVariable("myVariable");
if(myVariable != null)
Log.d(TAG, "myLayout was MylayoutBinding1 or myLayoutBinding2 or myLayoutBinding3");
else
Log.d(TAG, "mylayout was not one of the 3 layouts");
Sorry for this (very) long post but thank's for reading it and for any future answers !
I am not sure if I get it but what I do is I declare a variable in the layout like this:
<data>
<variable
name="variableName"
type="bla.bla.VariableType"/>
</data>
Then first you get your layout binding in onCreate:
binding = DataBindingUtil.inflate(inflater, R.layout.your_layout, container, false);
Then you should send a variable to the layout in code like:
binding.setVariableName(yourVariable);
Then you can retrieve it like:
binding.getVariableName();
why ViewDataBinding Doesn't have getMethodVariable?
consider the normal scenario without binding. you have a text container layout. how will you include another layout inside it in a normal activity.
my_text_container.xml:(Normal activity)
You inflate this layout to your java file using R.layout.my_text_container, you won't inflate every layout to a different view to access textview inside it. just one inflate and go with it.
So the same procedure is followed in DataBinding as well, when you use include tag ,the respective fields will be inside MyTextContainerLayoutBinding.
you don't have to inflate it again and again with activity name like MyLayout1Binding, MyLayout2Binding. that's why getting variable name become unnecessary over here.
I think adding variability to Binding , will be more helpful in a place where you just want to display data. Data from one model across multiple included layouts,more helpful with Listviews,Recyclerviews(where layout use data from models).
you can't provide different type for different layouts ,if you are going include one layout to another.(Remember that this binds both layout to single name (parent_activity in your case container)).
Eg:
UserProfileModel:
public class UserProfileModel {
String firstname;
String lastname;
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
}
my_layout_1.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">
<data>
<variable name="user"
type="com.hourglass.lingaraj.bindingusingvariables.UserProfileModel" />
</data>
<LinearLayout
android:orientation="vertical"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:visibility="visible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.firstname}"
style="#style/mystyle_1"/>
<include layout ="#layout/my_layout_2"
app:user="#{user}" />
</LinearLayout>
</layout>
my_layout_2.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.hourglass.lingaraj.bindingusingvariables.UserProfileModel"/>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:visibility="visible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.lastname}"
style="#style/mystyle_2"/>
</RelativeLayout>
</layout>
MainActivity.java:
public class MainActivity extends AppCompatActivity {
MyLayout1Binding layout_binding;
UserProfileModel data_model;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_layout_1);
data_model = new UserProfileModel();
data_model.setFirstname("Lingaraj");
data_model.setLastname("Sankaravelu");
layout_binding = DataBindingUtil.inflate(getLayoutInflater(),R.layout.my_layout_1,null,false);
layout_binding = DataBindingUtil.setContentView(this,R.layout.my_layout_1);
layout_binding.setUser(data_model);
}
}
As you see we are using only one model to populate data across included layout. So there is no need get Variable name to verify which model is loaded as it's Binded to single layout.
There is no workaround to achieve this, us of I have tried.
So my answer will be No you can't get a variable name from binding
References.
No More findViewById
Android Data Binding: That Thing
Android Data Binding: Adding some variability
Android Data Binding: Express Yourself

Categories

Resources