I am developing an open source text masker. You can click here to see source code.
My problem is that, when I wrap my custom edit text with TextInputLayout, onTextChanged is being triggered twice only for first letter. Then it works as expected.
This "twice call" breaks my logic. Do you guys have any idea what might be the problem? Since it is being used by other developers, I don't want to fix it with hacky solution. I need to find out the problem.
I set text manually after removing text watcher, then I add text watcher again.
Here is my main logic;
This method is being called only once;
private fun initTextWatcher() {
textWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
masker?.onTextChanged(s, start, count, before)
}
}
addTextChangedListener(textWatcher)
}
And this is how I set my text manually;
private fun setEditTextWithoutTriggerListener(newText: String) {
removeTextChangedListener(textWatcher)
onTextChangedListener?.invoke(newText) // This is a custom listener.
setText(newText)
setSelection(text?.length ?: 0) // This puts cursor at the end of the text.
addTextChangedListener(textWatcher)
}
To handle hint position like TextInputEditText does, I simply copied it's functions into mine.
override fun getHint(): CharSequence? {
val layoutHint = getTextInputLayout()?.hint
return layoutHint ?: super.getHint()
}
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? {
val ic = super.onCreateInputConnection(outAttrs)
if (ic != null && outAttrs.hintText == null) {
outAttrs.hintText = getHintFromLayout()
}
return ic
}
private fun getTextInputLayout(): TextInputLayout? {
var parent = this.parent
while (parent is View) {
if (parent is TextInputLayout) {
return parent
}
parent = parent.getParent()
}
return null
}
private fun getHintFromLayout(): CharSequence? {
val layout = getTextInputLayout()
return layout?.hint
}
And my class extends AppCompatEditText like TextInputEditText does.
If you are calling addTextChangedListener() from onCreate() or init() move the call into onResume() or any other function called later, otherwise onTextChanged() is triggered before the resume
Related
I am working on EditText where I am displaying pre filled country code in start lets say "+971", what I want is that if user trying to click and edit in EditText, he should not edit or delete "+971" and can only be able to put number after that like "+971 123445579" and if trying to delete number then "+971" should not get delete.
My code is given below, please guide me how can I achieve this. Thanks
private fun setupTextChangeListener() {
edit_text.textWatcherListener = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
setDatePickerIconOnDemand()
}
override fun afterTextChanged(s: Editable?) {
}
}
}
// In this method I am putting Start Drawable in EditText:
private fun setDatePickerIconOnDemand() {
edit_text.setDrawableStart(context.drawable(R.drawable.ic_earh_arrow_down_default))
edit_text.setDrawable()
}
You can add following lines of code in on onTextChanged callback
if (edit_text.length() < 5 || !edit_text.text!!.startsWith("+971 ")) {
edit_text.setText("+971 ")
edit_text.setSelection(edit_text.length())
}
What it will do is: It will check onTextChanged 'if your current text in edit text is only your country code, Set the country code in your editText and move cursor at the end.
I'm attempting to create a custom EditText that will implement a delay before executing onTextChanged.
class CustomEditText(context: Context, attributeSet: AttributeSet) : AppCompatEditText(context, attributeSet) {
private var millisDelay: Long = 500
private var timer: Timer? = null
override fun onTextChanged(
text: CharSequence?,
start: Int,
lengthBefore: Int,
lengthAfter: Int
) {
doDelay {
Log.d("somekoder", "Calling onTextChanged after $millisDelay milliseconds")
super.onTextChanged(text, start, lengthBefore, lengthAfter)
}
}
fun setDelay(millisDelay: Long){
this.millisDelay = millisDelay
}
private fun doDelay(then: () -> Unit){
timer?.cancel()
timer = Timer()
// Log.d("somekoder", "Got action. Waiting $millisDelay milliseconds.")
timer?.schedule(timerTask {
then.invoke()
}, millisDelay)
}
}
Here's in my MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
search.setDelay(1000)
search.addTextChangedListener {
Log.d("somekoder", "MainActivity: onTextChanged")
}
}
}
This is what my logs look like:
D/somekoder: MainActivity: onTextChanged
D/somekoder: MainActivity: onTextChanged
D/somekoder: MainActivity: onTextChanged
D/somekoder: MainActivity: onTextChanged
D/somekoder: Calling onTextChanged after 1000 milliseconds
MainActivity onTextChanged gets invoked even though I have a delay in there.
Can someone explain what I'm doing wrong?
Thanks in advance!
with addTextChangedListener() you can add multiple listener not like setOnclickListener where you can set only One listener at a time. As a general api implementation you have two listeners to your view. In your case both implemenation are different. Delay won't affect other listener execution.
onTextChanged method is a protected one which works like an inner listener for TextView subclasses. The default implementation is empty and it gets called when text is changed and after listeners. So it is completely independent of listeners.
If you want to delay listeners (not the text changing) you can do something like this. But I don't recommend it because I think it can cause problems especially in afterTextChanged (as it can change the text).
override fun addTextChangedListener(watcher: TextWatcher) {
super.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
doDelay {
watcher.beforeTextChanged(s, start, count, after)
}
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
doDelay {
watcher.onTextChanged(s, start, before, count)
}
}
override fun afterTextChanged(s: Editable?) {
doDelay {
watcher.afterTextChanged(s)
}
}
})
}
I would like to add full text from the editText into an array. But while running this code the array
values will be like first word , second word etc. How to get the full sentence and i want to add it
into an array(Already did it using interface)
Note : The code is placed on the recyclerview adapter ,
Where texBox is the EditText and examinationListener is the interface used
Here i shared the code. Please check it.
textBox.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
textFromBox = s.toString()
examinationListener.addAnswer(textFromBox)
}
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) {
textFromBox = textBox.text.toString()
examinationListener.addAnswer(textFromBox)
}
I created an array up to the list size. Each items of RecyclerView have an EditText.
val array = arrayOfNulls<String>(listSize)
We need to know the position of EditText to add to the array. Change interface like this:
interface ExaminationListener {
fun addAnswer(text: String, position: Int)
}
function addAnswer:
override fun addAnswer(text: String, position: Int) {
array[position] = text // we add every changes to the array.
}
This use is more useful. We don't need other methods:
textBox.doAfterTextChanged {
examinationListener.addAnswer(it.toString(), position) // you can use adapterPosition if that is in Viewholder
}
We create a sentence from array elements. You can see your full sentence:
val sentence = array.filterNotNull().joinToString (separator = " ") { it -> it }
Log.d("Sentence" , sentence)
Very Simple just add a TextWatcher to your EditText like:
val answerWatcher = object : TextWatcher {
override fun beforeTextChanged(value: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(value: CharSequence, start: Int, before: Int, count: Int) {
when {
// Here value is your full text from your EditText
value.toString().equals("I am a sentence", ignoreCase = true) -> {
}
else -> {
// Just a test condition
}
}
}
override fun afterTextChanged(s: Editable) {
}
}
// Add this TextWatcher to your EditText
edittext.addTextChangedListener(answerWatcher)
I have two edit text in my fragment where users can enter their age and weight. Based on age and weight I calculate how much water user should drink
When I try to call hide keyboard function on afterTextChanged, the keyboard gets hidden after user typed her first character.
How can I hide the keyboard when user actually finished typing?
P.S: there is a maxLength in my edit text 2 for age and 3 for weight, maybe this could be useful information when you are thinking about your recommendation
My code for edit texts with onChange extension function
//change listener extension for TextInputEditText
fun TextInputEditText.onChange(cb: (String) -> Unit) {
this.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
cb(s.toString())
hideKeyboard()
}
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
}
// change listener is called with agehandler function with user type age
// I check if the edit text is empty or not
binding.ageEditText.onChange {
if (binding.ageEditText.text.isNullOrEmpty()) {
Snackbar.make(binding.root, "Please type your age", Snackbar.LENGTH_SHORT).show()
} else dashboardViewModel.ageHandler(it)
}
fun hideKeyboard() {
val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view!!.windowToken, 0)
}
This may not be the cleanest way in the world to do things but I use a function to hide the keyboard when users press outside of the edit text. You could also look into hiding upon hitting the enter or submit key on the keyboard here
Otherwise to use these you would just say
editText.hideKeyBoardOnPressAway()
editText.addCharacterMax(2)
fun View.hideKeyBoardOnPressAway(){
this.onFocusChangeListener = keyboardHider
}
private val keyboardHider = View.OnFocusChangeListener { view, b ->
if (!b) { hideKeyboardFrom(view) }
}
private fun hideKeyboardFrom(view: View) {
val imm = view.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
fun EditText.addCharacterMax(max: Int){
filters = arrayOf(InputFilter.LengthFilter(max))
}
I am creating a verification screen with 4 EditText. And I create one common sub-class of TextWatcher in Kotlin: see below code (the code might be right or wrong).
When I use the below code it crashes the application.
view.etTwo.requestFocus()
Crash Log
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.widget.EditText.requestFocus()' on a null object reference
at com.mindfulness.greece.activity.ConfirmationCodeActivity$GenericTextWatcher.afterTextChanged(ConfirmationCodeActivity.kt:123)
at android.widget.TextView.sendAfterTextChanged(TextView.java:8007)
And here are the classes:
class GenericTextWatcher(var view: View) : TextWatcher {
override fun afterTextChanged(s: Editable?) {
val text: String = s.toString();
when(view.id) {
R.id.etOne -> {
if(text.length==1){
view.etTwo.requestFocus()
}
}
R.id.etTwo -> {
if(text.length==1){
view.etThree.requestFocus()
}
}
R.id.etThree -> {
if(text.length==1){
view.etFour.requestFocus()
}
}
R.id.etFour -> {}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
}
the problem is that you're trying to select etOne, etTwo, etThree & etFour from INSIDE the view you receive on TextWatcher.
The view you access in the function afterTextChanged(s: Editable?)
is already the editTexts you're willing to requestFocus() on.
The view you're accessing is one of the 4 TextViews you have. You need to change the logic here in order to have the access to other TextViews
view.etTwo is null as view is the EditText you're currently editing and it doesn't contain all other edittexts.
You could call view.getParent() list all views in parent, find the next one and focus on it. Lots of work!
Or to make it more generic you could extend EditText Like this:
fun EditText.onTextChange(onAfterTextChanged: OnAfterTextChangedListener) {
addTextChangedListener(object :TextWatcher{
private var text = ""
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
override fun afterTextChanged(s: Editable?) {
if (s?.length == 1) {
onAfterTextChanged.complete()
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
})
}
interface OnAfterTextChangedListener {
fun complete ()
}
Then in you activity where you have the edittexts, call something like this:
etOne.onTextChange(object: OnAfterTextChangedListener {
override fun complete() {
etTwo.requestFocus()
}
})
etTwo.onTextChange(object: OnAfterTextChangedListener {
override fun complete() {
etThree.requestFocus()
}
})
This creates a new method in EditText called onTextChange (could be called whatever). This method calls back to your OnAfterTextChangedListener in your Activity where you have access to all your edittexts. And it will be called afterTextChanged if the length of the text equals 1.
TextWatcher common class for multiple EditText
class GenericTextWatcher internal constructor(private val view: View) :
TextWatcher {
override fun afterTextChanged(editable: Editable) { // TODO Auto-generated method stub
val text = editable.toString()
when (view.id) {
R.id.otp1 -> if (text.length == 1) edit1.requestFocus()
R.id.otp2 -> if (text.length == 1) edit2.requestFocus() else if (text.isEmpty()) edit1.requestFocus()
R.id.otp3 -> if (text.length == 1) edit3.requestFocus() else if (text.isEmpty()) edit2.requestFocus()
R.id.otp4 -> if (text.isEmpty()) edit4.requestFocus()
}
}
override fun beforeTextChanged(
arg0: CharSequence,
arg1: Int,
arg2: Int,
arg3: Int
) { // TODO Auto-generated method stub
}
override fun onTextChanged(
arg0: CharSequence,
arg1: Int,
arg2: Int,
arg3: Int
) { // TODO Auto-generated method stub
}
}
Use into your class like this
edit1.addTextChangedListener(GenericTextWatcher(edit1))
edit2.addTextChangedListener(GenericTextWatcher(edit2))
edit3.addTextChangedListener(GenericTextWatcher(edit3))
edit4.addTextChangedListener(GenericTextWatcher(edit4))