I have this EditText in which I want the user to type a credit card number, so I want to format the string while the user is typing it, I specifically want the string to have a space every 4 numbers, like this:
xxxx xxxx xxxx xxxx
I found that I could use TextWatcher and onKeyUp but I couldn't understand how use it in a EditText, if some one could explain me I would really appreciate it, thanks.
Create a class that implements the ITextWatcher interface. Then add an instance of that class as a text changed listener...
public class CreditCardFormatter : Java.Lang.Object, ITextWatcher
{
private EditText _editText;
public CreditCardFormatter(EditText editText)
{
_editText = editText;
}
public void AfterTextChanged(IEditable s)
{
}
public void BeforeTextChanged(ICharSequence s, int start, int count, int after)
{
}
public void OnTextChanged(ICharSequence s, int start, int before, int count)
{
}
}
In your activity.. (or OnCreateView in a fragment)
public override void OnCreate()
{
// other code..
myEditText.AddTextChangedListener(new CreditCardFormatter(myEditText));
}
Then use the override methods to reformat the text to show what you need.
Related
I am just learning about RxJava in Android and the (supposed) excellent composition of MVVM, databinding and RxJava. Unfortunately I cannot bind an RxJava Observable directly to a View: need a LiveData.
So, I was wondering is there a way to implement Two-way databinding with RxJava?
So far I've attempted to write a BindingAdapter which adds a listener to the passed View and calls onNext on the Subject.
#BindingAdapter("rxText")
public static void bindReactiveText(EditText view, BehaviorSubject<String> text)
{
view.setText(text.getValue());
view.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
text.onNext(s.toString());
}
});
}
What this does is updates the Subject/model with any changes in the View, so model always has consistent value with the View. However, the binding is never triggered for any change in the Subject itself (ofcourse we'd have to compare new and old values to stop from looping).
Now, I did try to subscribe to the subject and call setText for each emission, but then I'd have to dispose the Observer. So what I did was also listened for View Attach State change: subscribe in onViewAttachedToWindow and dispose the observer in onViewDetachedFromWindow.
#BindingAdapter("rxText")
public static void bindReactiveText(EditText editText, BehaviorSubject<String> text)
{
setText(editText, text.getValue());
editText.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
private TextWatcher textWatcher = new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
text.onNext(s.toString());
}
};
private Disposable disposable;
#Override
public void onViewAttachedToWindow(View v) {
editText.addTextChangedListener(textWatcher);
disposable = text.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.filter(s -> !s.equals(editText.getText().toString()))
.subscribe(s -> setText(editText, text.getValue()));
}
#Override
public void onViewDetachedFromWindow(View v) {
disposable.dispose();
editText.removeTextChangedListener(textWatcher);
}
});
}
And while that does work in the intended way, I'm not sure if this is the best approach to implement Two-way binding via RxJava.
One of the immediate drawback that comes into mind is, Activity/Fragment cannot register a for a callback for most android Views. For instance, if I use the same approach for a Button and its click, and set up a listener in my Activity the binding will stop working.
I am still learning RxJava and its various operators and their uses, so maybe I'm missing something obvious or committing a goof, but I've been trying to working out another way to do this for a few days now, so far have not been able to think of one.
So my question: What is the best approach to implementing Two-way data binding with RxJava Observables.
To bind RxJava Observable directly to a View, you need to use ObservableField:
val name = ObservableField<String>()
//ex: name = "john"
Then put this in your xml:
<data>
<import type="android.databinding.ObservableField"/>
<variable
name="name"
type="ObservableField<String>" />
</data>
<TextView
android:text="#{name}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Reference: https://developer.android.com/topic/libraries/data-binding/observability
EDIT:
In case you need to apply it on a model instead of a single field, you can do this:
class User : BaseObservable() {
#get:Bindable
var firstName: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.firstName)
}
#get:Bindable
var lastName: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.lastName)
}
}
BR is the name of a class generated by Android Data Binding.
I would not recommend you to do it like that. While usage of Rx Java with data binding is good(LiveData is better though), your way of implementing it is quite overworked.
This is enough to bind it to the textView.
#BindingAdapter("rxText")
public static void bindReactiveText(EditText view, BehaviorSubject<String> text)
{
view.setText(text.getValue());
view.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
text.onNext(s.toString());
}
});
}
To subscribe to it and use it you should use a bit different approach. You should use it in view model that should look like this
public class MyViewModel extends ViewModel {
private CompositeDisposable subscriptions = new CompositeDisposable();
public BehaviorSubject<String> text = new BehaviorSubject<String>();
private void bind() {
subscriptions.add(
text.subscribe(text -> {
//do something
})
)
}
#Override
public void onCleared(){
subscriptions.clear()
}
}
and in layout use
...
<TextView
android:id="#+id/text"
style="#style/TextField.Subtitle3.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/small_margin"
app:rxText="#{vm.text}"
/>
...
and in fragment
public class MasterFragment extends Fragment {
private MyViewModel vm;
public void onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
ViewDataBinding binding = DataBindingUtil.inflate(inflater, layoutRes, container, false);
model = new ViewModelProvider(requireActivity()).get(MyViewModel.class);
binding.setVariable(BR.vm, this);
binding.executePendingBindings();
vm.bind();
return binding.root;
}
}
I would do something like this if I would use RxJava for that. I am using LiveData though.
Hope it helps.
I have 8 fragments, each inflating a layout with among others, an EditText wrapped inside a TextInputLayout. In the onCreateView, am implementing
EditText inputTextFrag1 = (EditText)findViewById(R.id.et_frag1);
inputTextFrag1.addTextChangedListener(new MyTextWatcher(inputTextFrag1));
Am also having to implement MyTextWatcher class in each fragment body as below:
private class MyTextWatcher implements TextWatcher {
private View view;
public MyTextWatcher(View view) {
this.view = view;
}
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
saveButton.getBackground().setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);
saveButton.setClickable(false);
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
saveButton.getBackground().setColorFilter(null);
switch (view.getId()) {
case R.id.et_frag1:
validateName();
break;
}
}
}
Where validateName();
private boolean validateName() {
if (inputTextFrag1 .getText().toString().trim().isEmpty()) {
mInputLayoutName.setError(getString(R.string.err_msg_name));
requestFocus(inputTextFrag1 );
return false;
} else {
mInputLayoutName.setErrorEnabled(false);
}
return true;
}
Is there a way to have just one MyTextWatcher class somewhere and one validateName() method to be called by each fragment instead of duplicating the same class/method 8 times. Thanks
Is this the correct way to place the TextWatcher class inside a BaseDialogFragment?
public abstract class BaseDialogFragment extends DialogFragment{
private class MyTextWatcher implements TextWatcher {
private View view;
public MyTextWatcher(View view) {
this.view = view;
}
#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) {
}
}
}
What logic goes into the beforeTextChanged and afterTextChanged methods of the TextWatcher?
You can create a BaseFragment that will be extended by your fragments.
Therefore, you can manage your TextWatcher inside this BaseFragment and consequently the fragments which have this heritage, will receive your expected logic.
As in the following example:
BaseFragment.class
public abstract class BaseFragment extends Fragment implements TextWatcher {
EditText editText;
Button button;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//inflate your edit text
...
//inflate your button
...
editText.addTextChangedListener(this);
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//text watcher listener
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//text watcher listener
}
#Override
public void afterTextChanged(Editable s) {
//text watcher listener
}
}
YourFragment.class
public class YourFragment extends BaseFragment {
...
}
No need for duplication. In your current implementation, it seems your MyTextWatcher class is an inner class of another class (probably fragment class). In this way of implementation you can't share it among all fragment classes.
However if you define your MyTextWatcher class as a standalone class, you can then use it for all fragment classes. To do this, you should only be using the variables and class members that have been declared in scope of the class being defined. In your case saveButton variable doesn't belong to MyTextWatcher class (it's accessible from the outer scope), in such cases, you should import them via the constructor method.
private class MyTextWatcher implements TextWatcher {
private View view;
private Button saveButton;
public MyTextWatcher(View view, Button saveButton) {
this.view = view;
this.saveButton = saveButton;
}
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
saveButton.getBackground().setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);
saveButton.setClickable(false);
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
saveButton.getBackground().setColorFilter(null);
switch (view.getId()) {
case R.id.et_frag1:
validateName();
break;
}
}
}
You can now instantiate this class 8 times for your 8 fragments.
However, #Bruno Vieira's solution is better (i.e. using a base fragment class).
i have a question. Does class like this:
public class AmountTextWatcher implements TextWatcher {
private final TextView amountTo;
public AmountTextWatcher(TextView amountTo) {
this.amountTo = amountTo;
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
Pattern mPattern = Pattern.compile(PatternHolder.amountPattern());
Matcher matcher = mPattern.matcher(s.toString());
if (matcher.find()) {
amountTo.setTextColor(Color.BLACK);
} else {
amountTo.setTextColor(Color.RED);
if (s.length() > 0) s.delete(s.length() - 1, s.length());
}
}
}
create a memory leak? do i need to hold a reference to such to it and clear it in onDestroy (or onDestroyView, whatever).
cheers
Wojtek
Your AmountTextWatcher is held by a TextView. The AmountTextWatcher holds onto the same TextView. Hence, these two objects, from a garbage collection standpoint, are part of the same object graph. The lifespan of those objects will be determined by who holds onto either of them.
If your only reference to the AmountTextWatcher is from the TextView, then the AmountTextWatcher will live as long as the TextView does. Normally, that will be as long as the activity that is showing the TextView lives.
IOW, your AmountTextWatcher will not be a memory leak, so long as the TextView it is associated with is not itself leaked.
I need to work with validations.For that I would like to give my type of validation as an attribute/property while am creating my EditText field.
android:inputType="email"
my customized is
validation:requiredType="phone"
in my edit text field how can I achieve this in Eclipse.
maybe this can help you
public abstract class TextValidator
implements TextWatcher {
private final TextView textView;
public TextValidator(TextView textView) {
this.textView = textView;
}
public abstract void validate(TextView textView, String text);
#Override
final public void afterTextChanged(Editable s) {
String text = textView.getText().toString();
validate(textView, text);
}
#Override
final public void beforeTextChanged(CharSequence s, int start, int count, int after) {
/* Don't care */
}
#Override
final public void onTextChanged(CharSequence s, int start, int before, int count) {
/* Don't care */
}
}
Just use it like this:
editText.addTextChangedListener(new TextValidator(editText) {
#Override
public void validate (TextView textView, String text){
/* Validation code here */
}
});
To create a library:
File > New Module
select Android Library
To use the library add it as a dependancy:
File > Project Structure > Modules > Dependencies
Then add the module (android library) as a module dependency.
Run your project. It will work.
hope it helps
I am doing a dictionary app based on this project. This is my AutoCompleteTextView:
searchAutoCompleteTextView.setThreshold(1);
searchAutoCompleteTextView.setAdapter(new WordAutoComplite(ZhuangDictActivity.this, null));
searchAutoCompleteTextView.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
if (s.length() > 0) {
char key = GB2Alpha.Char2Alpha(s.charAt(0));
if (currentChar != key) {
currentChar = key;
tableName = DatabaseHelper.transTableName(key);
}
}
}
});
My CursorAdapter:
#Override
public Cursor runQueryOnBackgroundThread(CharSequence word) {
return databaseHelper.queryAutoComplete(tableName, word.toString(), DEFAULT_AUTOCOMPLETE_LIMIT);
}
/* (non-Javadoc)
* #see android.widget.CursorAdapter#convertToString(android.database.Cursor)
*/
#Override
public CharSequence convertToString(Cursor cursor) {
return cursor.getString(0);
}
The DatabaseHelper.java is available here and here is the complete code of my activity.
I noticed a bug. Let say I search "apple" and I exit the app. Then I reenter the app and search "apple" again, this time the result cannot be found. In order to solve this problem, I have to type another alphabet randomly other than "a". If after I exit the app and return to it, I search "book" before searching "apple", I will be able to get the result. Seem like the app lost connection to the database. How should I solve this problem?