How to correctly 2-way-bind numeric for Android editText

Something must be missing for me. Every example I saw with a two-way binding for Android is based on String in the backup data for any user input, like EditText .

Processing something, rather than a string, seems somewhat ... inelegant. For example, if I have a double in my domain model that needs to be editable, the best binding I came up with requires a ViewModel with a surprisingly large amount of code for the interaction between the model and EditText .

Did I miss something? Should I really need 30 lines of code for the EditText interface with a double? For discussion, consider a currency field, represented as a double, in a two-way EditText border:

 <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:inputType="numberDecimal" android:text="@={fragModel.startBucks}" android:id="@+id/editText2"/> 

And here is the ViewModel that I had to build to give the EditText a string to bind to.

 @Bindable private String startBucksString; private double localBucks; public String getStartBucksString() { double domainBucks = cd.getStartBucks(); // Ignore incoming change of less than rounding error if( Math.abs(localBucks - domainBucks) < .001 ) return startBucksString; startBucksString = ""; if( domainBucks != 0) startBucksString = String.format("$%.2f", domainBucks); return startBucksString; } public void setStartBucksString(String inBuckstr) { double calcBucks=0; inBuckstr = inBuckstr.replaceAll( "[^\\d.]", "" ); try { calcBucks = Double.parseDouble(inBuckstr); } catch( NumberFormatException e) { return; } // Neglect outgoing change of less than rounding error if( Math.abs(localBucks - calcBucks) < .001 ) return; startBucksString = String.format("$%.2f", calcBucks); localBucks = calcBucks; cd.setStartBucks(calcBucks); notifyPropertyChanged(BR.startBucksString); } 

Here I wrote a simple, compiled example of two-way binding with ViewModel . This illustrates the difficulty that I experienced with constantly updating the float in the domain model - in the end, I decided that there was no way to do this without writing a special TextWatcher for each domain.

+5
source share
1 answer

My approach is to delay calling the notifyPropertyChanged method using descriptors. Thus, while the user is typing, the code does not run, and 2.5 seconds after the user has stopped to enter the last character, the notificationPropertyChanged method will be called.

The visual effect is cool, and the user can write numbers as they wish.

See two examples:

Usage can use this compact (?) Code for each field:

 // // g1FuelCostYear field // private double g1FuelCostYear; @Bindable public String getG1FuelCostYear() { return Double.valueOf(g1FuelCostYear).toString(); } private Handler hG1FuelCostYearDelay = null; public void setG1FuelCostYear(String g1FuelCostYear) { // Delayed notification hadler creation if (hG1FuelCostYearDelay == null) { hG1FuelCostYearDelay = new Handler() { @Override public void handleMessage(Message msg) { notifyPropertyChanged(it.techgest.airetcc2.BR.g1FuelCostYear); } }; } else { // For each call remove pending notifications hG1FuelCostYearDelay.removeCallbacksAndMessages(null); } // Data conversion logic try { this.g1FuelCostYear = Double.parseDouble(g1FuelCostYear); } catch (Exception ex) { this.g1FuelCostYear = 0.0; log(ex); } // New delayed field notification (other old notifications are removed before) hG1FuelCostYearDelay.sendEmptyMessageDelayed(0,2500); } 

This code is useful when you are using a currency converter or a percentage converter. The user can write a simple double code, convert the code to a currency string. If the setter is called using a currency string, the code can also convert it as a double.

 // // g1FuelCostYear field // private double g1FuelCostYear; @Bindable public String getG1FuelCostYear() { NumberFormat nf = NumberFormat.getCurrencyInstance(); return nf.format(this.g1FuelCostYear); //return Double.valueOf(g1FuelCostYear).toString(); } private Handler hG1FuelCostYearDelay = null; public void setG1FuelCostYear(String g1FuelCostYear) { if (hG1FuelCostYearDelay == null) { hG1FuelCostYearDelay = new Handler() { @Override public void handleMessage(Message msg) { notifyPropertyChanged(it.techgest.airetcc2.BR.g1FuelCostYear); } }; } else { hG1FuelCostYearDelay.removeCallbacksAndMessages(null); } boolean success = false; try { NumberFormat nf = NumberFormat.getCurrencyInstance(); this.g1FuelCostYear = nf.parse(g1FuelCostYear).doubleValue(); success = true; } catch (Exception ex) { this.g1FuelCostYear = 0.0; log(ex); } if (!success) { try { this.g1FuelCostYear = Double.parseDouble(g1FuelCostYear); success = true; } catch (Exception ex) { this.g1FuelCostYear = 0.0; log(ex); } } updateG1FuelConsumption(); hG1FuelCostYearDelay.sendEmptyMessageDelayed(0,2500); } 
+1
source

All Articles