Skip to content

Instantly share code, notes, and snippets.

@skleeschulte
Forked from Nilzor/TwoWayBoundString.java
Last active February 26, 2016 03:33
Show Gist options
  • Save skleeschulte/3d33b29cd3bca8ce41ce to your computer and use it in GitHub Desktop.
Save skleeschulte/3d33b29cd3bca8ce41ce to your computer and use it in GitHub Desktop.
Observable String for two-way data binding, based on Fabio Collini's article and Frode Nilsen's gist. This version works without an extra id in the resources and with all TextViews.
import android.databinding.BaseObservable;
import android.databinding.BindingAdapter;
import android.databinding.BindingConversion;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.TextView;
import java.io.Serializable;
import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;
/**
* An observable class that holds a String and is capable of two-way data binding with TextViews and
* its descendants (e.g. EditText). To be used with Android's Data Binding Library.
*
* Based on:
* - Fabio Collini's great blog article about two-way data binding
* (https://medium.com/@fabioCollini/android-data-binding-f9f9d3afc761) and
* - Frode Nilsen's implementation of TwoWayBoundString
* (https://gist.github.com/Nilzor/05a7db14819d09e45262).
*
* This version works without defining an id in the resources and with all TextViews. It also
* implements the Parcelable interface.
* For EditTexts, the cursor is moved to the end after a value change in the model.
*
* For Android API version < 9, <code>Set<Object> mEmitters</code> must be replaced with e.g.
* <code>Map<Object, Boolean> mEmitters</code>, because <code>Collections.newSetFromMap(...)</code>
* is not available.
*
* @author Stefan Kleeschulte
*/
public class TwoWayBoundString extends BaseObservable implements TextWatcher, Parcelable, Serializable {
static final long serialVersionUID = 1L;
private String mValue;
private Set<Object> mEmitters = Collections.newSetFromMap(new WeakHashMap<Object, Boolean>());
/**
* Creates a TwoWayBoundString with the given initial value.
*
* @param value the initial value for the TwoWayBoundString
*/
public TwoWayBoundString(String value) {
mValue = value;
}
/**
* Creates a TwoWayBoundString with the initial value of <code>null</code>.
*/
public TwoWayBoundString() {
}
/**
* @return the stored value.
*/
public String get() {
return mValue;
}
/**
* Set the stored value.
*/
public void set(String value) {
if (mValue == null && value == null) return;
if (mValue == null || !mValue.equals(value)) {
mValue = value;
notifyChange();
}
}
/**
* Register this TwoWayBoundString instance as TextWatcher for the given TextView if it is not
* already registered.
*
* @param textView the TextView to register on
*/
public void registerTextWatcher(TextView textView) {
if (!mEmitters.contains(textView)) {
mEmitters.add(textView);
textView.addTextChangedListener(this);
}
}
/**
* BindingAdapter enabling two-way-binding for <code>android:text</code> attributes (for
* TextViews, including EditTexts): Updates TextView if TwoWayBoundString value changed and
* registers TwoWayBoundString as TextWatcher for the TextView.
*
* @param textView the TextView to update
* @param twoWayBoundString the TwoWayBoundString instance that changed and that shall be
* notified about changes in the TextView
*/
@BindingAdapter("android:text")
public static void bindTextView(TextView textView, final TwoWayBoundString twoWayBoundString) {
twoWayBoundString.registerTextWatcher(textView);
String newValue = twoWayBoundString.get();
if (!textView.getText().toString().equals(newValue)) {
textView.setText(newValue);
if (textView instanceof EditText) {
((EditText) textView).setSelection(textView.getText().length());
}
}
}
/**
* BindingConversion enabling one-way binding for other attributes.
*
* @param twoWayBoundString the TwoWayBoundString instance that changed
* @return plain String object
*/
@BindingConversion
public static String twoWayBoundStringToString(TwoWayBoundString twoWayBoundString) {
return twoWayBoundString.get();
}
/**
* TextWatcher interface.
*/
@Override
public void afterTextChanged(Editable s) {
}
/**
* TextWatcher interface.
*/
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
/**
* TextWatcher interface - updates the value of this TwoWayBoundString instance when a bound
* TextView's text changes.
*/
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
this.set(s.toString());
}
/**
* Parcelable interface.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Parcelable interface.
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mValue);
}
/**
* Parcelable interface.
*/
public static final Parcelable.Creator<TwoWayBoundString> CREATOR = new Parcelable.Creator<TwoWayBoundString>() {
@Override
public TwoWayBoundString createFromParcel(Parcel source) {
return new TwoWayBoundString(source.readString());
}
@Override
public TwoWayBoundString[] newArray(int size) {
return new TwoWayBoundString[size];
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment