I have implemented the new Android data-binding, and after implementing realised that it does not support two-way binding. I have tried to solve this manually but I am struggling to find a good solution to use when binding to an EditText.
In my layout I have this view:
<EditText
android:id="#+id/firstname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords|textNoSuggestions"
android:text="#{statement.firstName}"/>
Another view is also showing the results:
<TextView
style="#style/Text.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{statement.firstName}"/>
In my fragment I create the binding like this:
FragmentStatementPersonaliaBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_statement_personalia, container, false);
binding.setStatement(mCurrentStatement);
This works and puts the current value of firstName in the EditText. The problem is how to update the model when the text changes. I tried putting an OnTextChanged-listener on the editText and updating the model. This created a loop killing my app (model-update updates the GUI, which calls textChanged times infinity). Next I tried to only notify when real changes occured like this:
#Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
boolean changed = !TextUtils.equals(this.firstName, firstName);
this.firstName = firstName;
if(changed) {
notifyPropertyChanged(BR.firstName);
}
}
This worked better, but everytime I write a letter, the GUI is updated and for som reason the edit-cursor is moved to the front.
Any suggestions would be welcome
EDIT 04.05.16:
Android Data binding now supports two way-binding automatically!
Simply replace:
android:text="#{viewModel.address}"
with:
android:text="#={viewModel.address}"
in an EditText for instance and you get two-way binding. Make sure you update to the latest version of Android Studio/gradle/build-tools to enable this.
(PREVIOUS ANSWER):
I tried Bhavdip Pathar's solution, but this failed to update other views I had bound to the same variable. I solved this a different way, by creating my own EditText:
public class BindableEditText extends EditText{
public BindableEditText(Context context) {
super(context);
}
public BindableEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BindableEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private boolean isInititalized = false;
#Override
public void setText(CharSequence text, BufferType type) {
//Initialization
if(!isInititalized){
super.setText(text, type);
if(type == BufferType.EDITABLE){
isInititalized = true;
}
return;
}
//No change
if(TextUtils.equals(getText(), text)){
return;
}
//Change
int prevCaretPosition = getSelectionEnd();
super.setText(text, type);
setSelection(prevCaretPosition);
}}
With this solution you can update the model any way you want (TextWatcher, OnTextChangedListener etc), and it takes care of the infinite update loop for you. With this solution the model-setter can be implemented simply as:
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
This puts less code in the model-class (you can keep the listeners in your Fragment).
I would appreciate any comments, improvements or other/better solutions to my problem
This is now supported in Android Studio 2.1+ when using the gradle plugin 2.1+
Simply change the EditText's text attribute from #{} to #={} like this:
<EditText
android:id="#+id/firstname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords|textNoSuggestions"
android:text="#={statement.firstName}"/>
for more info, see: https://halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-android/
#Gober The android data-binding support the two way binding. Therefore you do not need to make it manually. As you tried by putting the OnTextChanged-listener on the editText. It should update the model.
I tried putting an OnTextChanged-listener on the editText and updating
the model. This created a loop killing my app (model-update updates
the GUI, which calls textChanged times infinity).
It’s worth noting that binding frameworks that implement two-way binding would normally do this check for you…
Here’s the example of modified view model, which does not raise a data binding notification if the change originated in the watcher:
Let’s create a SimpleTextWatcher that only requires only one method to be overridden:
public abstract class SimpleTextWatcher implements TextWatcher {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
onTextChanged(s.toString());
}
public abstract void onTextChanged(String newValue);
}
Next, in the view model we can create a method that exposes the watcher. The watcher will be configured to pass the changed value of the control to the view model:
#Bindable
public TextWatcher getOnUsernameChanged() {
return new SimpleTextWatcher() {
#Override
public void onTextChanged(String newValue) {
setUsername(newValue);
}
};
}
Finally, in the view we can bind the watcher to the EditText using addTextChangeListener:
<!-- most attributes removed -->
<EditText
android:id="#+id/input_username"
android:addTextChangedListener="#{viewModel.onUsernameChanged}"/>
Here is the implementation of the view Model that resolve the notification infinity.
public class LoginViewModel extends BaseObservable {
private String username;
private String password;
private boolean isInNotification = false;
private Command loginCommand;
public LoginViewModel(){
loginCommand = new Command() {
#Override
public void onExecute() {
Log.d("db", String.format("username=%s;password=%s", username, password));
}
};
}
#Bindable
public String getUsername() {
return this.username;
}
#Bindable
public String getPassword() {
return this.password;
}
public Command getLoginCommand() { return loginCommand; }
public void setUsername(String username) {
this.username = username;
if (!isInNotification)
notifyPropertyChanged(com.petermajor.databinding.BR.username);
}
public void setPassword(String password) {
this.password = password;
if (!isInNotification)
notifyPropertyChanged(com.petermajor.databinding.BR.password);
}
#Bindable
public TextWatcher getOnUsernameChanged() {
return new SimpleTextWatcher() {
#Override
public void onTextChanged(String newValue) {
isInNotification = true;
setUsername(newValue);
isInNotification = false;
}
};
}
#Bindable
public TextWatcher getOnPasswordChanged() {
return new SimpleTextWatcher() {
#Override
public void onTextChanged(String newValue) {
isInNotification = true;
setPassword(newValue);
isInNotification = false;
}
};
}
}
I hope this is what you are looking and sure can help you. Thanks
There is a simpler solution. Just avoid updating field if it hadn't really changed.
#Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
if(this.firstName.equals(firstName))
return;
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
POJO:
public class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public User(String firstName, String lastName) {
this.firstName.set(firstName);
this.lastName.set(lastName);
}
public TextWatcherAdapter firstNameWatcher = new TextWatcherAdapter(firstName);
public TextWatcherAdapter lastNameWatcher = new TextWatcherAdapter(lastName);
}
Layout:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.firstName, default=First_NAME}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.lastName, default=LAST_NAME}"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/editFirstName"
android:text="#{user.firstNameWatcher.value}"
android:addTextChangedListener="#{user.firstNameWatcher}"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/editLastName"
android:text="#{user.lastNameWatcher.value}"
android:addTextChangedListener="#{user.lastNameWatcher}"/>
Watcher:
public class TextWatcherAdapter implements TextWatcher {
public final ObservableField<String> value =
new ObservableField<>();
private final ObservableField<String> field;
private boolean isInEditMode = false;
public TextWatcherAdapter(ObservableField<String> f) {
this.field = f;
field.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback(){
#Override
public void onPropertyChanged(Observable sender, int propertyId) {
if (isInEditMode){
return;
}
value.set(field.get());
}
});
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//
}
#Override public void afterTextChanged(Editable s) {
if (!Objects.equals(field.get(), s.toString())) {
isInEditMode = true;
field.set(s.toString());
isInEditMode = false;
}
}
}
I struggled to find a full example of 2-way databinding. I hope this helps.
The full documentation is here:
https://developer.android.com/topic/libraries/data-binding/index.html
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="item"
type="com.example.abc.twowaydatabinding.Item" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#={item.name}"
android:textSize="20sp" />
<Switch
android:id="#+id/switch_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="#={item.checked}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="change"
android:onClick="button_onClick"/>
</LinearLayout>
</layout>
Item.java:
import android.databinding.BaseObservable;
import android.databinding.Bindable;
public class Item extends BaseObservable {
private String name;
private Boolean checked;
#Bindable
public String getName() {
return this.name;
}
#Bindable
public Boolean getChecked() {
return this.checked;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
public void setChecked(Boolean checked) {
this.checked = checked;
notifyPropertyChanged(BR.checked);
}
}
MainActivity.java:
public class MainActivity extends AppCompatActivity {
public Item item;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
item = new Item();
item.setChecked(true);
item.setName("a");
/* By default, a Binding class will be generated based on the name of the layout file,
converting it to Pascal case and suffixing “Binding” to it.
The above layout file was activity_main.xml so the generate class was ActivityMainBinding */
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setItem(item);
}
public void button_onClick(View v) {
item.setChecked(!item.getChecked());
item.setName(item.getName() + "a");
}
}
build.gradle:
android {
...
dataBinding{
enabled=true
}
}
Related
I have an activity that shows one text-view in that:
<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="Users"
type="com.example.mvvm.model.UserInfoModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.MainActivity">
<TextView
android:id="#+id/tv_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="50dp"
android:text="#{Users.name,default = hello}"/>
</LinearLayout>
</layout>
And my UserInfoModel class:
public class UserInfoModel {
private String name;
public UserInfoModel() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And in my Activity:
public class MainActivity extends AppCompatActivity {
ActivityMainBinding binding;
UserInfoModel users;
TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
textView = binding.tvMain;
users = new UserInfoModel();
users.setName("Stack");
viewAction();
binding.setLifecycleOwner(this);
binding.setUsers(users);
}
}
My Problem is, When I Change name in viewAction method like this:
private void viewAction() {
textView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
users.setName("Change Text");
}
});
}
My UI doesn't change and text-view still showing Stack. I try to use binding.notifyPropertyChanged(id) but it doesn't affect. Where is my mistake? How can I change the data?
thanks for your attention.
You can just make your fields observable in the models.
public class UserInfoModel {
private ObservableField<String> name;
public UserInfoModel() {
this.name = new ObservableField<>();
}
public ObservableField<String> getName() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
}
Actually the problem is that generated binding class doesn't know when property is changed. You should notify it. First, your viewmodel can be inherited from BaseObservable class, then you have to add #Bindable annotation to getters in your viewmodel and call notifyPropertyChanged inside setters with appropriate id from BR class(it's going to be generated for you by databinding library, just add #Bindable annotation. for instance notifyPropertyChanged(BR.name) ). Also you should pay attention how you store "binding" variable in MainActivity class.It can lead to memory leaks. You can investigate that issue. See library like databindingPropertyDelegate.
Something like this
public class UserInfoModel extends BaseObservable{
#Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name)
}
}
I am making a password creation screen, and I want to show a password strength indicator as they type.
My layout looks like this:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.example.PasswordViewModel" />
</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"
app:textChangedListener="#{viewModel.passwordTextWatcher}" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{viewModel.passwordQuality}"/>
</LinearLayout>
</layout>
PasswordViewModel.java is like below
public class PasswordViewModel extends BaseObservable {
private String password;
#Bindable
public String getPasswordQuality() {
if (password == null || password.isEmpty()) {
return "Enter a password";
} else if (password.equals("password")) {
return "Very bad";
} else if (password.length() < 6) {
return "Short";
} else {
return "Okay";
}
}
public void setPassword(String password) {
this.password = password;
notifyPropertyChanged(BR.passwordQuality);
}
#Bindable
public TextWatcher getPasswordTextWatcher() {
return new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Do nothing.
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
setPassword(s.toString());
}
#Override
public void afterTextChanged(Editable s) {
// Do nothing.
}
};
}
And Finally
public class EditTextBindingAdapters {
#BindingAdapter("textChangedListener")
public static void bindTextWatcher(EditText editText, TextWatcher textWatcher) {
editText.addTextChangedWatcher(textWatcher);
}
}
But when I start to write onTextChanged method is not calling! can anyone help me to resolve this issue? thanks, in advance.
According your description of problem, it seems you did not initialized binding in your activity. if this is the case then
First check that you have initialized the binding in your activity and set the ViewModel on it like below
ActivityMainBinding activityMainBinding = null;
PasswordViewModel passwordViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
passwordViewModel = new PasswordViewModel();
activityMainBinding.setViewModel(passwordViewModel);
}
Now the second problem is addTextChangedWatcher, add addTextChangedListener in place of that.
like this
public class EditTextBindingAdapters {
#BindingAdapter("textChangedListener")
public static void bindTextWatcher(EditText editText, TextWatcher textWatcher) {
editText.addTextChangedListener(textWatcher);
}
}
I have just created a demo project for learning and the MVVM and how to use the Mvvm in our project.
but I have found an error while running the project
error: cannot find symbol class ViewModel
error: package ViewModel does not exist
error: package ViewModel does not exist
error: package ViewModel does not exist
and here is my code
public class User extends BaseObservable {
String email,password;
boolean isDataValidate;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isDataValidate() {
return !TextUtils.isEmpty(getEmail())&& Patterns.EMAIL_ADDRESS.matcher(getEmail()).matches()&&
getPassword().length()>6;
}
public void setDataValidate(boolean dataValidate) {
isDataValidate = dataValidate;
}
}
and Here is my ViewModel class
public class LoginViewModel extends ViewModel {
private User user;
private LoginResultCallback loginResultCallback;
public LoginViewModel(LoginResultCallback loginResultCallback){
this.loginResultCallback=loginResultCallback;
this.user=new User();
}
public String getEmailText1(){
return user.getEmail();
}
public String getPasswordText1(){
return user.getPassword();
}
public TextWatcher getEmailText(){
return new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
user.setEmail(editable.toString());
}
};
}
public TextWatcher getPasswordText(){
return new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
user.setPassword(editable.toString());
}
};
}
public void onLoginClicked(View view)
{
if (user.isDataValidate()){
loginResultCallback.onSuccess("Login was Successfull");
}
else{
loginResultCallback.onError("Login Invalid Credential");
}
}
}
and here is my MainActivity
public class MainActivity extends AppCompatActivity implements LoginResultCallback {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding= DataBindingUtil.setContentView(this,R.layout.activity_main);
LoginViewModel loginViewModel=new LoginViewModel(this);
activityMainBinding.setViewModel(loginViewModel);
//activityMainBinding.setViewModel(ViewModelProviders.of(this,new LoginViewModelFactory(this)).get(LoginViewModel.class));
}
#Override
public void onSuccess(String Message) {
Toast.makeText(this, ""+Message, Toast.LENGTH_SHORT).show();
}
#Override
public void onError(String Error) {
Toast.makeText(this, ""+Error, Toast.LENGTH_SHORT).show();
}
}
and here is viewModelFactory class
public class LoginViewModelFactory extends ViewModelProvider.NewInstanceFactory {
private LoginResultCallback loginResultCallback;
public LoginViewModelFactory(LoginResultCallback loginResultCallback)
{
this.loginResultCallback=loginResultCallback;
}
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
return (T) new LoginViewModel(loginResultCallback);
}
}
and XML is here
<?xml version="1.0" encoding="utf-8"?>
<data>
<variable
name="viewModel"
type="com.example.designinfpattern.ViewModel.LoginViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="#+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:addTextChangedListener="#{viewModel.emailText}"
android:hint="Enter Your account Email or Username"
/>
<EditText
android:id="#+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Your account password"
app:addTextChangedListener="#{viewModel.passwordText}" />
</LinearLayout>
<Button
android:id="#+id/login_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{viewModel::onLoginClicked}"
android:text="Login" />
</LinearLayout>
Here is the dependency which I am using on the build Gradle file:
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
I don't know how to solve this error and I'm doing R&D but not find a proper solution. Please help me out to solve the problem
thanks in advance
You need to rename the ViewModel folder (package) from ViewModel to viewmodel
Add the following in your build.gradle
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
// alternatively - just ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:2.1.0"
Today, I faced the same problem in MVVM. I fixed this. Everything was working fine and suddenly I started getting this same error. Then I recall what changes I recently made. Then I realised I moved my Activity files from one Package to another after this I stared facing the problem. In layout, I changed my viewmodel packages accordingly and Bingo! Sharing this it may help and save time of others in case same thing happened in future.
I am developing a sort of monitor which should update a lot of imageviews (fake leds) basing on what i receive from a machine and let the user know the current machine state.
Is there a way to set on the variable a value listener which trigger a method when the value change in which i can update my UI?
Thanks everybody for the help and the comments;
I find out Data Binding was the best option in my case, so after a few attempts i've been able to build a sample project where i've got a an handler which every 1sec increment an integer value by 1.
After that i check if the value is pair or dispair and i set a boolen true or false.
I set up a layout file in which i've got 2 text view and a toggle button. The first text view text is binded to a simple string with my name, the second one is binded to the integer which is incremented each second (a timer) and the Toggle Button is binded to the Boolean.
Here's the Code:
MainActivity
public class MainActivity extends AppCompatActivity {
ActivityMainBinding binding;
android.os.Handler Handler;
User user;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
user = new User("Lorenzo", "Gangemi", 0);
binding.setUser(user);
useHandler();
}
public void useHandler() {
if (Handler != null) {
Handler.postDelayed(RunableHandler, 1000);
Handler.removeCallbacks(RunableHandler);
}
try {
Handler = new Handler();
} catch (Exception e) {
Handler = new Handler(Looper.getMainLooper());
}
Handler.postDelayed(RunableHandler, 1000);
}
private Runnable RunableHandler = new Runnable() {
#Override
public void run() {
user.increment_i();
binding.setUser(user);
useHandler();
}
};
}
User
public class User {
private final String firstName;
private final String lastName;
private boolean pari;
private int i;
public User(String firstName, String lastName, int i) {
this.firstName = firstName;
this.lastName = lastName;
this.i = i;
setPari(checkPari());
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getI() {
return i;
}
public void increment_i()
{
i++;
setPari(checkPari());
}
public boolean checkPari()
{
return i%2==0;
}
public boolean isPari() {
return pari;
}
public void setPari(boolean pari) {
this.pari = pari;
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="mdptech.databinding.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{Integer.toString(user.i)}" />
<ToggleButton
android:text="ToggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/toggleButton"
android:drawableBottom="#drawable/toggle"
android:background="#android:color/transparent"
android:textOff="OFF"
android:textOn="ON"
android:checked="#{user.pari ? true : false}"/>
</LinearLayout>
</layout>
Everything works fine.
As you can see from the code, in the handler Runnable i call the method for increment the timer, but i have to call binding.setUser(user); too for updating the layout.
Is there a way to make this update happen automatically and not becouse i call a method?
That's not a big problem becouse i can create a separate handler which loops every 100ms (for ex.) and in his runnable call binding.setUser(user);, But it will speed up my work a lot.
Thank you.
I am trying to set the limit of an integer with a minimum value of 0 and maximum of 10 in android with the help of databinding.
For that i have a bindable adapter which set the value of an integer with two listener one increase the value and the other decrease it. Now finally i wanted to set the limit of that integer, minimum of 0 and maximum of 10.
#BindingAdapter("quantity")
public static void setQuantityText(TextView textView, int quantity) {
textView.setText(String.valueOf(quantity));
}
public static class ListenerIncrease implements View.OnClickListener {
private FragmentBinding binding;
public ListenerIncrease(FragmentBinding binding) {
this.binding = binding;
}
#Override
public void onClick(View v) {
int quantity = binding.getQuantity();
binding.setQuantity(++quantity);
}
}
public static class ListenerDecrease implements View.OnClickListener {
private FragmentBinding binding;
public ListenerDecrease(FragmentBinding binding) {
this.binding = binding;
}
#Override
public void onClick(View v) {
int quantity = binding.getQuantity();
binding.setQuantity(--quantity);
}
}
I think it would be easier if you handle the clicks a little differently. The lambda expression part only works in Android Studio 2.1 and above.
<Button android:onClick="#{(view)->Handlers.increment(view, 10)}" .../>
<Button android:onClick="#{(view)->Handlers.decrement(view, 0)}" .../>
<TextView app:quantity="#{quantity}"/>
And then your handler class has:
public static void increment(View view, int max) {
FragmentBinding binding = DataBindingUtil.findBinding(view);
binding.setQuantity(Math.max(max, binding.getQuantity() + 1));
}
public static void decrement(View view, int min) {
FragmentBinding binding = DataBindingUtil.findBinding(view);
binding.setQuantity(Math.min(min, binding.getQuantity() - 1));
}
Alternatively, you can use full-blown two-way binding. In the forthcoming Android Studio 2.2, you'll be able to do this:
<Button android:onClick="#{()->Handlers.increment(quantityView, 10)}" .../>
<Button android:onClick="#{()->Handlers.decrement(quantityView, 0)}" .../>
<TextView android:id="#+id/quantityView" app:quantity="#={`` + quantity}"/>
And then your handler class has:
private static int getCurrentIntValue(TextView view) {
try {
return Integer.parseInt(view.getText().toString());
} catch (NumberFormatException e) {
return 0;
}
}
public static void increment(TextView view, int max) {
int value = getCurrentIntValue(view);
binding.setQuantity(Math.max(max, value + 1));
}
public static void decrement(View view, int min) {
int value = getCurrentIntValue(view);
binding.setQuantity(Math.min(min, value - 1));
}
The trick added in Android Studio 2.2 is the support for the conversions for string concatenation with empty string. It is a useful shortcut. Without that (Android Studio 2.1), you'll need to add your own two-way binding for integer-to-TextView text:
#InverseBindingAdapter(attribute = "quantity")
public static int getQuantity(TextView view) {
return getCurrentIntValue(view);
}
and here's a simplified binding adapter. Use the one in TextViewBindingAdapter.java as a template if you need to add text watchers to the same TextView:
#BindingAdapter("onQuantityChanged")
public static void setQuanityWatcher(TextView view,
final InverseBindingListener quantityChanged) {
TextWatcher newTextWatcher;
if (quantityChanged == null) {
newTextWatcher = null;
} else {
newTextWatcher = new TextWatcher() {
#Override
public void afterTextChanged(Editable s) {
quantityChanged.onChange();
}
// others are empty...
}
}
TextWatcher oldTextWatcher = ListenerUtil.trackListener(
view, newTextWatcher, R.id.textWatcher);
if (oldTextWatcher != null) {
view.removeTextChangeListener(oldTextWatcher);
}
if (newTextWatcher != null) {
view.addTextChangedListener(newTextWatcher);
}
}
Note that I haven't compiled any of this, so there may be typos.