Android two-way databinding, textview not updating - android

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>

Related

How to bind Spinner data using view model (DataBinding)

I'm beginner to data binding.I'm trying to get spinner items from my view model using data binding and set the values in android:entries ,But the thing is my spinner didn't showing items.
Here is my layout
<layout>
<data>
<import type="com.saddan.sanidadvegetalsyscomed.viewmodel.TableDataViewModel"/>
<variable
name="model"
type="TableDataViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#ffffff"
android:layout_margin="20dp"
android:padding="5dp"
tools:context=".Fragment.First_Form_Fragment">
<Spinner
style="#android:style/Widget.TextView.SpinnerItem"
android:id="#+id/ext_tipo_inspection"
android:layout_width="320dp"
android:layout_height="#dimen/EditTextFieldHeight"
android:entries="#{model.spinerInspecion}"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Here is my view model class
public class TableDataViewModel extends AndroidViewModel
{
private String TAG=getClass().getSimpleName();
private UserAccessToken userAccessToken;
private SanidadDAO sanidadDAO;
private Context context;
private LiveData<List<String>> spinnerItem;
private LiveData<List<String>> spinerInspecion;
public TableDataViewModel(#NonNull Application application)
{
super(application);
userAccessToken=new UserAccessToken(application);
SanidadVegetalDatabase database = SanidadVegetalDatabase.getDatabase(application);
sanidadDAO = database.getDao();
context=application;
spinerInspecion=getmCommonData("tipoinspeccion");
if(spinerInspecion!=null)
{
Log.d(TAG, "TableDataViewModel: yrs");
}
}
public LiveData<List<String>> getmCommonData(String queryType)
{
spinnerItem=sanidadDAO.getType(queryType);
//Toast.makeText(context, ""+mCommonData, Toast.LENGTH_SHORT).show();
return spinnerItem;
}
public LiveData<List<String>> getSpinerInspecion()
{
return spinerInspecion;
}
public void setSpinerInspecion(LiveData<List<String>> spinerInspecion)
{
this.spinerInspecion = spinerInspecion;
}
#Override
protected void onCleared()
{
super.onCleared();
}
}
and here is my Fragment's onViewCreated method where I initialize the binding
tableDataViewModel= new ViewModelProvider(getActivityNonNull()).get(TableDataViewModel.class);
binding.setModel(tableDataViewModel);
Problem here is you are not setting value to spinerInspecion you are assigning it a new LiveData. This is why observer never get called on the instance. Modify your method as:-
public List<String> getmCommonData(String queryType) {
spinnerItem=sanidadDAO.getType(queryType);
return spinnerItem;
}
and then set value as :-
spinerInspecion.setValue(getmCommonData("tipoinspeccion"));

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.

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

DataBinding Android, custom setters, Doesnt work?

I have simple layout and viewModel. I want to connect them with each other but they dont connect.Problem of above problem is in logs there is no error and my app also doesn't crash.
Here my layout:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="progressView"
type="uz.iutlab.ictnews.viewModel.ProgressViewModel" />
<variable
name="viewModel"
type="uz.iutlab.ictnews.viewModel.DetailFragmentViewModel" />
</data>
<RelativeLayout
android:id="#+id/activity_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
tools:context="uz.iutlab.ictnews.view.fragment.DetailFragment">
<RelativeLayout
android:id="#+id/fragment_detail_post_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="#dimen/collapsing_image_height">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.makeramen.roundedimageview.RoundedImageView
android:id="#+id/fragment_detail_image_post"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="#{viewModel.image}"
app:layout_collapseMode="parallax"
app:setContext="#{viewModel.context}" />
<android.support.v7.widget.Toolbar
android:id="#+id/fragment_detail_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_collapseMode="pin" />
<TextView
android:id="#+id/fragment_detail_title_post"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="#dimen/spacing_view_large"
android:text="#{viewModel.title}"
android:textColor="#android:color/white"
android:textSize="#dimen/font_size_title_huge" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
</RelativeLayout>
</RelativeLayout>
Here is my ViewModel class
public class DetailFragmentViewModel extends BaseObservable {
private Post mPost;
private Context mContext;
public DetailFragmentViewModel(Post mPost,Context mContext) {
this.mPost = mPost;
this.mContext = mContext;
}
public String getTitle() {
return mPost.getTitle().getRendered();
}
public Context getContext () {
return mContext;
}
public String getImage() {
if (mPost.getMedia().getSource_url() != null) {
return mPost.getMedia().getSource_url();
} else {
return null;
}
}
#BindingAdapter({"android:src","setContext"})
public static void downloadImage (RoundedImageView imageView,String url,Context context) {
if (url!=null) {
Picasso.with(context).load(url).into(imageView);
} else {
imageView.setImageResource(R.drawable.placeholder);
}
}
}
There is no error, There is no crashes. App works normally but there is no any title , any image.I tried this one instead overriding its method writing own but doesn't work.
#BindingAdapter({"bind:imageUrl","setContext"})
public static void downloadImage (RoundedImageView imageView,String url,Context context) {
if (url!=null) {
Picasso.with(context).load(url).into(imageView);
} else {
imageView.setImageResource(R.drawable.placeholder);
}
}
In addition to above . I also check it with debug, above methods are not called.
You should better remove app:setContext="#{viewModel.context}" and get it from the view in your adapter. Also you need to use attribute names without a namespace; so instead of bind:imageUrl only use imageUrl.
#BindingAdapter("imageUrl")
public static void downloadImage (RoundedImageView imageView, String url) {
if (url != null) {
Picasso.with(imageView.getContext()).load(url).into(imageView);
} else {
imageView.setImageResource(R.drawable.placeholder);
}
}
But since Picasso works asynchronously, you might end up with an image after you already set it to R.drawable.placeholder again.
Eventually you could also have a look at the generated java sources for the binding and see if your BindingAdapter is called somewhere.
try like this:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/apk/res-auto"
>
.....
<com.makeramen.roundedimageview.RoundedImageView
android:id="#+id/fragment_detail_image_post"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
bind:src="#{viewModel.image}"
app:layout_collapseMode="parallax"
bind:setContext="#{viewModel.context}" />
.....
</layout>
#BindingAdapter({"bind:loadUrl"})
public static void downloadImage(RoundedImageViewimageView, String url) {
if (url != null) {
Picasso.with(imageView.getContext()).load(url).into(imageView);
} else {
imageView.setImageResource(R.mipmap.ic_launcher);
}
}
The problem was not in my ViewModel or in my xml file everything is correct there is no syntax error.Problem is here this is fragment and I have connected them together my fragment with my viewModel.I made mistake in creating binding here you can see it. This is my not working one
FragmentStudentLifeBinding binding = DataBindingUtil.inflate(inflater,R.layout.fragment_student_life,container,false);
Here is correct one
FragmentStudentLifeBinding binding = DataBindingUtil.inflate(inflater,R.layout.fragment_student_life,container,true);
I missed to attach fragment to activity that is why my binding hadn't been working for 21 days.Hope it will help to someone.

Categories

Resources