Problem: I've been trying unsuccessfully to append a file extension which follows the cursor in real-time as a user types.
What I've tried: I've been trying to use the .addTextChangedListener() which I think is correct, but having an issue trying to make the idea happen. Debugging shows is continually looping and so it gives the appearance of freezing. I looked at a couple post here on the forum but the ones suggested when I entered this post title dealt with prefixes and not suffixes.
EditText field with highlighted area representing text to follow right of cursor:
One of my poor attempts to make it work:
private void setupAddDBEditText(){
mDBToAdd.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.toString().contains(".db"))
mDBToAdd.setText(s.toString().substring(0,s.toString().length()-3));
mDBToAdd.setText(s + ".db");
}
}
You will need to remove the listener when you change the text (that's why you are getting an infinite loop).
You can make a custom TextWatcher to handle this sort of thing (below) that removes itself as a watcher, changes the text and puts the cursor in the right place, then adds itself back.
public class FileExtensionTextWatcher implements TextWatcher {
private final String extension;
private final EditText text;
FileExtensionTextWatcher(EditText t, String ext) {
extension = ext;
text = t;
}
// you may want to change around the logic in here to allow for
// odd entries or other changes - but the overall approach should
// be similar
private String removeAutoExtension(String s) {
String ext = "." + extension;
if( s.contains(ext) ) {
int ei = s.lastIndexOf(ext);
if( ei == 0 ) return "";
String trailingChar = "";
if( ei < s.length() - ext.length() ) {
trailingChar = s.substring(ei+ext.length());
}
return s.substring(0,ei) + trailingChar;
}
else {
return s;
}
}
#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 txt) {
if( txt == null ) return;
String currentText = txt.toString();
String filename = removeAutoExtension(currentText);
int initCursor = filename.length();
String displayedText;
if( filename.isEmpty() ) {
displayedText = filename;
}
else {
displayedText = filename + "." + extension;
}
text.removeTextChangedListener(this);
txt.clear();
txt.insert(0, displayedText);
text.setSelection(initCursor);
text.addTextChangedListener(this);
}
}
You would then use it like this
EditText e = findViewById(R.id.edit_text);
e.addTextChangedListener(new FileExtensionTextWatcher(e, "db"));
Here is a demo GIF of how it looks.
There are some changes you may want to make to handle things like whether the user is allowed to enter their own extension (this form does not allow it - but you could change that with some extra logic in the custom watcher).
I have an EditText field with a Customer Text Watcher on it. In a piece of code I need to change the value in the EditText which I do using .setText("whatever").
The problem is as soon as I make that change the afterTextChanged method gets called which created an infinite loop. How can I change the text without it triggering afterTextChanged?
I need the text in the afterTextChanged method so don't suggest removing the TextWatcher.
Short answer
You can check which View currently has the focus to distinguish between user and program triggered events.
EditText myEditText = (EditText) findViewById(R.id.myEditText);
myEditText.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (myEditText.hasFocus()) {
// is only executed if the EditText was directly changed by the user
}
}
//...
});
Long answer
As an addition to the short answer:
In case myEditText already has the focus when you programmatically change the text you should call clearFocus(), then you call setText(...) and after you you re-request the focus. It would be a good idea to put that in a utility function:
void updateText(EditText editText, String text) {
boolean focussed = editText.hasFocus();
if (focussed) {
editText.clearFocus();
}
editText.setText(text);
if (focussed) {
editText.requestFocus();
}
}
For Kotlin:
Since Kotlin supports extension functions your utility function could look like this:
fun EditText.updateText(text: String) {
val focussed = hasFocus()
if (focussed) {
clearFocus()
}
setText(text)
if (focussed) {
requestFocus()
}
}
You could unregister the watcher, and then re-register it.
Alternatively, you could set a flag so that your watcher knows when you have just changed the text yourself (and therefore should ignore it).
Java:
public class MyTextWatcher implements TextWatcher {
private EditText editText;
// Pass the EditText instance to TextWatcher by constructor
public MyTextWatcher(EditText editText) {
this.editText = editText;
}
#Override
public void afterTextChanged(Editable e) {
// Unregister self before update
editText.removeTextChangedListener(this);
// The trick to update text smoothly.
e.replace(0, e.length(), e.toString());
// Re-register self after update
editText.addTextChangedListener(this);
}
...
}
Kotlin:
class MyTextWatcher(private val editText: EditText) : TextWatcher {
override fun afterTextChanged(e: Editable) {
editText.removeTextChangedListener(this)
e.replace(0, e.length, e.toString())
editText.addTextChangedListener(this)
}
...
}
Usage:
et_text.addTextChangedListener(new MyTextWatcher(et_text));
You may feel a little bit lag when entering text rapidly if you are using editText.setText() instead of editable.replace().
Easy trick to fix ... as long a your logic to derive the new edit text value is idempotent (which it probably would be, but just saying). In your listener method, only modify the edit text if the current value is different than the last time you modified the value.
e.g.,
TextWatcher tw = new TextWatcher() {
private String lastValue = "";
#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) {
// Return value of getNewValue() must only depend
// on the input and not previous state
String newValue = getNewValue(editText.getText().toString());
if (!newValue.equals(lastValue)) {
lastValue = newValue;
editText.setText(newValue);
}
}
};
You can use Kotlin DSL syntax to have the generic solution for this:
fun TextView.applyWithDisabledTextWatcher(textWatcher: TextWatcher, codeBlock: TextView.() -> Unit) {
this.removeTextChangedListener(textWatcher)
codeBlock()
this.addTextChangedListener(textWatcher)
}
And inside your TextWatcher, you can use it as:
editText.applyWithDisabledTextWatcher(this) {
text = formField.name
}
I use that way:
mEditText.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 (mEditText.isFocused()) { //<-- check if is focused
mEditText.setTag(true);
}
}
});
And every time you need to change text programatically, first clear the focus
mEditText.clearFocus();
mEditText.setText(lastAddress.complement);
The problem can be easily solved using tag filed and you don't even have to deal with editText's focus.
Setting the text and the tag programmatically
editText.tag = "dummyTag"
editText.setText("whatever")
editText.tag = null
Checking for the tag in onTextChanged
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (editText.tag == null) {
// your code
}
}
This works good for me
EditText inputFileName; // = (EditText)findViewbyId(R.id...)
inputFileName.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
//unregistering for event in order to prevent infinity loop
inputFileName.removeTextChangedListener(this);
//changing input's text
String regex = "[^a-z0-9A-Z\\s_\\-]";
String fileName = s.toString();
fileName = fileName.replaceAll(regex, "");
s.replace(0, s.length(), fileName); //here is setting new text
Log.d("tag", "----> FINAL FILE NAME: " + fileName);
//registering back for text changes
inputFileName.addTextChangedListener(this);
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
public void onTextChanged(CharSequence s, int start, int before, int count) { }
});
If you need to stay focused on EditText change text you could request focus:
if (getCurrentFocus() == editText) {
editText.clearFocus();
editText.setText("...");
editText.requestFocus();
}
It's easy just do it like this
editText.addTextChangedListener(object : TextWatcher {
private var isEditing = false
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
if(!isEditing){
isEditing = true
editText.setText("Hello World!")
isEditing = false
}
}
})
in this way it don't stock in infinite loop
try this logic:
I wanted to setText("") without going to infinite loop and this code works for me. I hope you can modify this to fit your requirement
final EditText text= (EditText)findViewById(R.id.text);
text.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.toString().isEmpty())return;
text.setText("");
//your code
}
});
Here's a handy class that provides a simpler interface than TextWatcher for the normal case of wanting to see changes as they occur. It also allows for ignoring the next change as the OP requested.
public class EditTexts {
public final static class EditTextChangeListener implements TextWatcher {
private final Consumer<String> onEditTextChanged;
private boolean ignoreNextChange = false;
public EditTextChangeListener(Consumer<String> onEditTextChanged){
this.onEditTextChanged = onEditTextChanged;
}
public void ignoreNextChange(){
ignoreNextChange = true;
}
#Override public void beforeTextChanged(CharSequence __, int ___, int ____, int _____) { }
#Override public void onTextChanged(CharSequence __, int ___, int ____, int _____) { }
#Override public void afterTextChanged(Editable s) {
if (ignoreNextChange){
ignoreNextChange = false;
} else {
onEditTextChanged.accept(s.toString());
}
}
}
}
Use it like this:
EditTexts.EditTextChangeListener listener = new EditTexts.EditTextChangeListener(s -> doSomethingWithString(s));
editText.addTextChangedListener(listener);
Whenever you want to modify the contents of editText without causing a cascade of recursive edits, do this:
listener.ignoreNextChange();
editText.setText("whatever"); // this won't trigger the listener
My variant:
public class CustomEditText extends AppCompatEditText{
TextWatcher l;
public CustomEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setOnTextChangeListener(TextWatcher l) {
try {
removeTextChangedListener(this.l);
} catch (Throwable e) {}
addTextChangedListener(l);
this.l = l;
}
public void setNewText(CharSequence s) {
final TextWatcher l = this.l;
setOnTextChangeListener(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) {
}
});
setText(s);
post(new Runnable() {
#Override
public void run() {
setOnTextChangeListener(l);
}
});
}
}
Set listeners only using setOnTextChangeListener() and set text only using setNewText (I wanted to override setText(), but it is final)
I've created an abstract class which mitigates the cyclic issue of when a modification to the EditText is made via a TextWatcher.
/**
* An extension of TextWatcher which stops further callbacks being called as a result of a change
* happening within the callbacks themselves.
*/
public abstract class EditableTextWatcher implements TextWatcher {
private boolean editing;
#Override
public final void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (editing)
return;
editing = true;
try {
beforeTextChange(s, start, count, after);
} finally {
editing = false;
}
}
abstract void beforeTextChange(CharSequence s, int start, int count, int after);
#Override
public final void onTextChanged(CharSequence s, int start, int before, int count) {
if (editing)
return;
editing = true;
try {
onTextChange(s, start, before, count);
} finally {
editing = false;
}
}
abstract void onTextChange(CharSequence s, int start, int before, int count);
#Override
public final void afterTextChanged(Editable s) {
if (editing)
return;
editing = true;
try {
afterTextChange(s);
} finally {
editing = false;
}
}
public boolean isEditing() {
return editing;
}
abstract void afterTextChange(Editable s);
}
Very simple, set text with this method
void updateText(EditText et, String text) {
if (!et.getText().toString().equals(text))
et.setText(text);
}
My solution for this is a lot like the others only it's my custom spin on it using viewbindings
I created the following TextWatcher
class ControlledTextWatcher(
private val parent: TextView,
private val onChange: ((text: CharSequence?, start: Int, before: Int, count: Int) -> Unit)?,
private val beforeChange: ((text: CharSequence?, start: Int, count: Int, after: Int) -> Unit)? = null,
private val afterChange: ((editable: Editable?) -> Unit)? = null
) : TextWatcher {
init {
parent.addTextChangedListener(this)
}
private var enabled = true
var text: String?
get() = parent.value
set(value) {
this.enabled = false
parent.text = value
this.enabled = true
}
var res: Int
get() = throw RuntimeException("String resource cannot be retrieved after being set")
set(value) {
parent.text = parent.context.getString(value)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
if (enabled)
beforeChange?.invoke(s, start, count, after)
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (enabled)
onChange?.invoke(s, start, before, count)
}
override fun afterTextChanged(s: Editable?) {
if (enabled)
afterChange?.invoke(s)
}
fun detach() {
parent.removeTextChangedListener(this)
}
}
and I use it mainly with view bindings like so
class TestActivity : AppCompatActivity() {
class TestActivity : AppCompatActivity() {
private lateinit var binding: ActivityTestBinding
private val edit by lazy { ControlledTextWatcher(binding.text, this::textChanged }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTestBinding.inflate(layoutInflater)
setContentView(binding.root)
}
so when I wish to make changes to the actual EditText I use the text or res attribute of the ControlledTextWatcher like so:
edit.text = "hello world" //this does not trigger the text watcher
but when the user alters the EditText it will trigger
unfortunatelly with this solution if you want to alter other parameters of the EditText, you either have to get the original EditText through bindings or copy those functions to the ControlledTextWatcher
also you have to be careful when making changes in afterChange because the change is posted to the TextView so you may end up with an endless loop
You should ensure your implementation of text changes is stable and does not change the text if no change is needed. Normally that would be any content that's already been through the watcher once.
The most common mistake is to set a new text in the associated EditText or the Editable even though the text was not actually changes.
On top of that, if you make your changes to the Editable instead of some specific View, you can easily resuse your watcher, and also you can test it in isolation with some unit tests to ensure it has the outcome you want.
Since Editable is an interface you could even use a dummy implementation of it that throws a RuntimeException if any of its methods are called that try to change its contents, when testing content that should be stable.
My way to do the thing:
In the write segment
EditText e_q;
e_q = (EditText) parentView.findViewWithTag("Bla" + i);
int id=e_q.getId();
e_q.setId(-1);
e_q.setText("abcd...");
e_q.setId(id);
The listener
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
int id = view.getId();
if(id==-1)return;
....
Works anyway.
I have multiple EditTexts and I want to change the input of all of them at the same time, as I modify only one.(all of them take decimal numbers as input)
I stored the EditTexts in an array named 'editTexts'.
Here's what I tried
//Set the listener for each element
for (int i=0; i<editTexts.length; i++) {
final int finalI = i;
editTexts[i].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) {
//if the editText which is currently edited is empty, set the input for all the rest to be '0.0'
if (editTexts[finalI].getText().toString().trim().length() == 0) {
for(EditText e : editTexts) {
if (e == editTexts[finalI])
continue;
e.setText("0.0");
}
} else {
float no = Float.parseFloat(s.toString() + "");
//Set the input of all the other editTexts to be the decimal number entered, multiplied by 2
for(EditText e : editTexts){
if(e == editTexts[finalI])
continue;
e.setText(no*2+"");
}
}
}
#Override
public void afterTextChanged(Editable s) {
}
})
}
In this case the multiplication coefficient is just an example, it's not always gonna be 2. I used it just to test it out.
For some reason, when I change the input value, the app freezes.
Any help? Thanks!
Use LiveData to store your user input values.
Once it's value changes you can set value to each EditText. I think it is an easy way to implement.
Try it like this:
// et_x1, et_x2 and et_x3 are ids of EditTexts
//set inputType for all EditTexts as numberDecimal
EditText editText1 = findViewById(R.id.et_x1);
final EditText editText2 = findViewById(R.id.et_x2);
final EditText editText3 = findViewById(R.id.et_x3);
editText1.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) {
String value = s.toString();
double x;
if (!value.equals("")) {
x = Double.parseDouble(value);
} else {
x = 0.0;
}
editText2.setText(Editable.Factory.getInstance().newEditable((String.valueOf(Math.pow(x, 2)))));
editText3.setText(
Editable.Factory.getInstance().newEditable((String.valueOf(Math.pow(x, 3)))));
}
#Override
public void afterTextChanged(Editable s) {
}
});
Hope it helps you!
I'm having problems to get the position of my View inside a ListView when TextWatcher triggers for changes in EditText.
Each CardView has two EditTexts and two Spinners. When I make some change in the values for the name of the product (the EditText in the left) and for the spinners, my code get the correctly the position of the CardView in the list.
However, when I change the value of the price by typing it, my code cannot get it's position.
The position of the CardView is gotten in the line...
final int posicao = Integer.parseInt(consumableInfo.getName()), which consumableInfo is the class listed in my Adapater, and consumableInfo.getName gets the name of the card, which is equal to the position of the card. Like "0", "1", "2"...
This happens because everytime I call...
holder.mAutoCompleteTextView.setOnItemClickListener for the AutoCompleteEditText on the left;
holder.mDivideConsumableSpinner.setOnItemSelectedListener for each spinner;
...my code iterates again over BindData. However, when I call...
holder.mConsumablePriceTextView.addTextChangedListener(priceTextWatcher) for the EditText on the right;
... my code DO NOT iterates again.
I'm trying to find another way to get it's position, but I'm having problems with that. Maybe forcing a way to posicao get the value, or creating a customTextWatcher that implements TextWatcher and gets consumableInfo as a parameter.
public class ConsumableAdapter extends RecyclerView.Adapter<ConsumableAdapter.ConsumableViewHolder> {
/*...some code ommited...*/
int posicaoGlobal;
public ConsumableAdapter(Context context, List<ConsumableInfo> contactList) {...}/*...some code ommited...*/
}
public class ConsumableViewHolder extends RecyclerView.ViewHolder {
public AutoCompleteTextView mAutoCompleteTextView;
public Spinner mDivideConsumableSpinner;
public Spinner mUnitsConsumableSpinner;
public EditText mConsumablePriceTextView;
public ConsumableViewHolder(View itemView) {
/*...*/
}
public void bindData(ConsumableInfo consumableInfo, ConsumableViewHolder holder, Context context) {
final int posicao = Integer.parseInt(consumableInfo.getName());
posicaoGlobal = posicao;
ArrayAdapter adapter = new ArrayAdapter(mContext, android.R.layout.select_dialog_item,
Constants.CONSUMABLE_CONSTANTS);
holder.mAutoCompleteTextView.setAdapter(adapter);
/* position is updated withmAutoCompleteTextView.setOnItemClickListener */
holder.mAutoCompleteTextView.setOnItemClickListener(new AdapterView.OnItemClickListener({
updateTotalPrice(posicao);
/*...*/
});
/*position is NOT updated with addTextChangedListener*/
holder.mConsumablePriceTextView.addTextChangedListener(priceTextWatcher);
/*position is updated with setOnItemSelectedListener in both Spinners*/
holder.mDivideConsumableSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
updateTotalPrice(posicao);
/*...*/
});
//product units
holder.mUnitsConsumableSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
updateTotalPrice(posicao);
/*...*/
});
}
private void updateTotalPrice(int posicao) {
/*...*/
mTotalPrice = getTotalPrice(BotequimActivity.mProductList, mPercent);
BotequimActivity.mTotalPriceTextView.setText(getTotalPriceString());
FormatStringAndText.setPriceTextViewSize(mTotalPrice, BotequimActivity.mTotalPriceTextView);
}
}
private void updateTotalPrice(int posicao, String priceString) {
/*...*/
BotequimActivity.mTotalPriceTextView.setText(getTotalPriceString());
FormatStringAndText.setPriceTextViewSize(mTotalPrice, BotequimActivity.mTotalPriceTextView);
}
private final TextWatcher priceTextWatcher = new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (count != 0) {
if (FormatStringAndText.isNumeric(s.toString())) {
mProductPriceBeforeChange = Double.parseDouble(s.toString());
}
}
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Toast.makeText(mContext, "posicao =" + posicaoGlobal, Toast.LENGTH_SHORT).show();
if (s.toString().length() == 0) {
updateTotalPrice(posicaoGlobal, "0.00");
} else {
if (!isAutoCompleteClicked) {
if (FormatStringAndText.isNumeric(s.toString())) {
mProductPriceAfterChange = Double.parseDouble(s.toString());BotequimActivity.mTotalPriceTextView.setText(getTotalPriceString());
// FormatStringAndText.setPriceTextViewSize(mTotalPrice, BotequimActivity.mTotalPriceTextView);
updateTotalPrice(posicaoGlobal, s.toString());
} else {
}
} else {
isAutoCompleteClicked = false;
}
}
}
#Override
public void afterTextChanged(Editable s) {
}
};
public Double getTotalPrice(ArrayList<Product> productList, Double percent) {
mTotalPrice = 0;
for (Product product : productList) {
mTotalPrice = mTotalPrice + percent * (product.getUnits() * (product.getDoublePrice()) / product.getDividedBy());
}
return mTotalPrice;
}
}
You need to save the position when you create the TextWatcher. I would do this with an inner subclass:
// this is an inner class so it will have an implicit reference to
// the adapter (ConsumableAdapter.this)
public class PriceTextWatcher implements TextWatcher {
private int mPos;
public PriceTextWatcher(int position) {
super();
mPos = position;
}
// now add your TextWatcher implementation here and use mPos for position
#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) {}
}
Now you can initialize the position when you create the TextWatcher:
holder.mConsumablePriceTextView.addTextChangedListener(new PriceTextWatcher(posicao));
You will have multiple TextWatchers instead of the single final TextWatcher you currently have, but that's the trade-off for getting the position value where it needs to be.
Solved. I had to call priceTextWatcher as an argument with it's constructor, just like AdapterView.OnItemSelectedListener(). The correct one is:
holder.mConsumablePriceTextView.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//...
}
//...methods inside here
}
I am doing a chat app and got stocked on how to make textWatcher which will push on the firebase data structure under user-typing
. I want to push a data structure wherein on the data structure you will see if the user is typing. when the user is typing the data structure under user-typing is true. if the user is not typing then it will become false. i tried this code but it seems wrong because every time i run the program. When I click the ediText. it will automatically make a data structure key for a user
final Firebase test = firebase.child("room-typing").push();
test.setValue("true");
final EditText editText = (EditText) findViewById(R.id.editText);
editText.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
}
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
public void onTextChanged(CharSequence s, int start,
int before, int count) {
if (s == editText) {
firebase.child("room-typing").child(test.getKey()).child("test").setValue("true");
} else {
firebase.child("room-typing").child(test.getKey()).child("test").setValue("false");
}
}
});
You're creating that new key yourself, by calling push(). From the documentation for push():
Generates a new child location using a unique key and returns a Firebase reference to it.
I added some comments to you code, to mark where things happen:
// This next line creates a new key
final Firebase test = firebase.child("room-typing").push();
test.setValue("true");
final EditText editText = (EditText) findViewById(R.id.editText);
editText.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
}
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
public void onTextChanged(CharSequence s, int start,
int before, int count) {
if (s == editText) {
// This next line uses the new key you created above
firebase.child("room-typing").child(test.getKey()).child("Onediver").setValue("true");
} else {
// As does the next line here
firebase.child("room-typing").child(test.getKey()).child("Onediver").setValue("false");
}
}
});
To prevent the creation of a new child, you should not call push, but depend on a known child, such as:
final Firebase test = firebase.child("room-typing").child("jaymee");
There are 2 ways you can do this. An easy way and there is an elegant way.
For elegant way look into: http://reactivex.io/documentation/operators/debounce.html
Here's the easy way:
Create somewhere a custom countdown timer:
public class MyCountDownTimmer extends CountDownTimer {
public MyCountDownTimmer(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
#Override public void onTick(long l) {
}
#Override public void onFinish() {
databaseReference.child("room-typing").child(room_name).child(user_id_or_name).setValue("false");
isTyping = false;
}
}
Declare your countdown timer
private MyCountDownTimmer isTypingTimmer = new MyCountDownTimmer(1000, 1000);
private boolean isTyping = false;
editText_message.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
isTypingTimmer.cancel();
isTypingTimmer.start();
if (!isTyping) {
databaseReference.child("room-typing").child(room_name).child(user_id_or_name).setValue("true");
isTyping = true;
}
}
});