Reputation: 7421
In Android we can use 2-way data-binding with @=
in front of variable. But, that variable is a double
. So for displaying it in EditText
, I need to convert it to String
using String.valueOf(pojo.value)
.
If I attach =
in front for two-way data binding it simply just not compile.
If I attach a onTextChanged
and set the value there, I looses the cursor. Is there any workaround?
Edit:
It worked with InverseBindingAdapter
but doesn't allow .
(period) to be typed.
Upvotes: 9
Views: 8200
Reputation: 8917
This works for me (2020):
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="@={`` + viewModel.currentStudent.age}" //key point!! Not single/double quote
android:inputType="number" />
Upvotes: 3
Reputation: 1861
Here's how I did it.
//-------------------------------------------------------------------------------------------------//
@BindingAdapter("app:text")
fun setDoubleInTextView(tv: TextView, dbl: Double?) {
try {
//This will occur when view is first created or when the leading zero is omitted
if (dbl == null && (tv.text.toString() == "" || tv.text.toString() == ".")) return
//Check to see what's already there
val tvDbl = tv.text.toString().toDouble()
//If it's the same as what we've just entered then return
// This is when then the double was typed rather than binded
if (tvDbl == dbl)
return
//If it's a new number then set it in the tv
tv.text = dbl?.toString()
} catch (nfe: java.lang.NumberFormatException) {
//This is usually caused when tv.text is blank and we've entered the first digit
tv.text = dbl?.toString() ?: ""
}//catch
}//setDoubleInTextView
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -//
@InverseBindingAdapter(attribute = "app:text")
fun getDoubleFromTextView(editText: TextView): Double? {
return try {
editText.text.toString().toDouble()
} catch (e: NumberFormatException) {
null
}//catch
}//getDoubleFromTextView
//-------------------------------------------------------------------------------------------------//
@BindingAdapter("textAttrChanged")
fun setTextChangeListener(editText: TextView, listener: InverseBindingListener) {
editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) = listener.onChange()
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
Log.d(TAG, "beforeTextChanged $p0")
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
Log.d(TAG, "onTextChanged $p0")
}
})
}
//-------------------------------------------------------------------------------------------------//
<EditText
style="@style/TextAppearance.MaterialComponents.Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/price"
android:inputType="numberDecimal"
app:text="@={viewModel.tempProductPrice}"/>
The EditText has the following setting to force the correct input
android:inputType="numberDecimal"
Upvotes: 2
Reputation: 1342
Your solution worked partially for me. I used the @BindingAdaptor and the 'Amount' variable value was getting reflected in edit text, but the OnTextChangeListner did not get triggered on value change. So I used TextWatcher in VM. The code for both VM and XML is here. Nothing to be written in Activity.
//VIEW MODEL
@SerializedName("Amount")
private Double Amount ;
@Bindable
public Double getAmount() {
return (Amount == null)? 0.0 : Amount;
}
public void setAmount(Double amount) {
Amount = amount;
notifyPropertyChanged(BR.amount);
}
public TextWatcher getAmountWatcher() {
return new SimpleTextWatcher() {
@Override
public void onTextChanged(String text) {
Amount =(text!=null&&text!="")?Double.valueOf(text):0.0;
}
};
}
@BindingAdapter({"userVal"})
public static void setVal(EditText editText, double newVal) {
String currentValue = editText.getText().toString();
try {
if (Double.valueOf(currentValue) != newVal) {
DecimalFormat decimalFormat = new DecimalFormat("#.##");
String val = decimalFormat.format(newVal);
editText.setText(val);
}
} catch (NumberFormatException exception) {
exception.printStackTrace();
}
}
//XML CODE
<com.mycompany.myproj.Widgets.MyEditText
android:id="@+id/expense_cost"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="numberDecimal"
android:maxLength="6"
android:selectAllOnFocus="true"
app:addTextChangedListener="@{expense.amountWatcher}"
app:userVal="@{expense.Amount}" />
Cheers
Upvotes: 0
Reputation: 7421
There is no simple solution to this. But, I went ahead and created a kind of changes which almost work like a 2-way binding.
My EditText was looking like this:
<EditText
android:id="@+id/amount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.1"
android:digits="0123456789."
android:gravity="end"
android:inputType="numberDecimal|numberSigned"
android:onTextChanged="@{() -> handler.valueAmountChanged(amount)}"
android:selectAllOnFocus="true"
android:text="0"
android:textColor="@color/selector_disabled_edit_text"
app:userVal="@{userAmount}" />
handler
is the instance of the activity. And it contains the valueAmountChanged(EditText editText)
method.
Now in your value amount checked, I am parsing that text string and storing it in the respective variable.
For me, it is looking something like this:
public void valueAmountChanged(EditText editText) {
double d = 0.0;
try {
String currentString = editText.getText().toString();
// Remove the 2nd dot if present
if (currentString.indexOf(".", currentString.indexOf(".") + 1) > 0)
editText.getText().delete(editText.getSelectionStart() - 1, editText.getSelectionEnd());
// Remove extra character after 2 decimal places
currentString = editText.getText().toString(); // get updated string
if (currentString.matches(".*\\.[0-9]{3}")) {
editText.getText().delete(currentString.indexOf(".") + 3, editText.length());
}
d = Double.valueOf(editText.getText().toString());
} catch (NumberFormatException e) {
}
userAmount = d; // this variable is set for binding
}
Now, as we change the userAmount
variable it will going to reflect as we have set the binding adapter with app:userVal
argument in the EditText
.
So, with binding adapter we check if the new value is not the current value, then update the value. Else, leave it as it is. We need to do this, because if user is typing and binding adapter updates the value, then it will loose is cursor position and will bring it to the front. So, this will save us from that.
@BindingAdapter({"userVal"})
public static void setVal(EditText editText, double newVal) {
String currentValue = editText.getText().toString();
try {
if (Double.valueOf(currentValue) != newVal) {
DecimalFormat decimalFormat = new DecimalFormat("#.##");
String val = decimalFormat.format(newVal);
editText.setText(val);
}
} catch (NumberFormatException exception) {
// Do nothing
}
}
This is a bit typical approach, I know. But, couldn't find any better than this. There is also very less documentation available and others are in the form of blog posts on medium, which should have been added to the official documentation.
Hope it helps someone.
Upvotes: 1