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)
}
}
})
}
Related
Am trying to do something when my EditText content is changed.
Here is 3 ways i find to do it.
1.
edittext.addTextChangedListener {
//my code
}
edittext.doAfterTextChanged {
//my code
}
edittext.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
//my code
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
})
All works perfectly for my purpose. Can somebody explain if there is any difference between each of them, any advantage or disadvantage using a particular way or simply all of them same?
1 and 3 are basically same thing. In kotling object : TextWatcher is not needed as it can wrap the implementation with lambda using just {}
edittext.addTextChangedListener {
//my code
}
What you did in the 3 with object : TextWatcher is same thing but the implemented functions afterTextChanged, beforeTextChanged and onTextChanged are visible.
And for 2, android in kotlin gives an inline function edittext.doAfterTextChanged which just does everything for you what you did on 3 using object : TextWatcher under the hood and gives access to only afterTextChanged functions implementation. This code is from androidx.core.widget package which shows what it's doing under the hood:
inline fun TextView.doAfterTextChanged(
crossinline action: (text: Editable?) -> Unit
) = addTextChangedListener(afterTextChanged = action)
I have been working on a stock keeping application as a demo to learn kotlin and android studio, I have added textchangedlistener in a recycler view item, which everytime it text is changed, gets information from Api and displays new item on textview. This works good for only one time, After that it keeps infinitely changing. please check video for better understanding : ScreenRecording .
Here is my rcv adapter :
class RecyclerViewAdapterU (val dataList:ArrayList<ModelClass>): RecyclerView.Adapter<RecyclerViewAdapterU.ViewHolder>() {
var _binding: UploadItemViewBinding? = null
val binding get() = _binding!!
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerViewAdapterU.ViewHolder {
val v =
LayoutInflater.from(parent.context).inflate(R.layout.upload_item_view, parent, false)
_binding = UploadItemViewBinding.bind(v)
return ViewHolder(binding.root)
}
override fun onBindViewHolder(holder: ViewHolder, #SuppressLint("RecyclerView") position: Int) {
bindItems(dataList[position])
holder.getStock()
holder.updateStockDetail()
}
fun bindItems(data: ModelClass) {
binding.apply {
itemquant.text=data.item_quant
uploadItemName.text = data.item_name
uploadMfg.text = data.mfg
skuStock.setText(data.item_stock.toString())
skuCode.setText(data.sku_code)
}
}
fun getUpdatedDetails(skucode:String,pos:Int){
val call: Call<List<ModelClass>>? =
ApiClient.instance?.myApi?.getfromsku(skucode)!!
call!!.enqueue(object : Callback<List<ModelClass>?> {
override fun onResponse(
call: Call<List<ModelClass>?>,
response: Response<List<ModelClass>?>
) {
val skuDetails=response.body()
if (skuDetails != null) {
dataList.removeAt(pos)
for (i in skuDetails){
println(i.item_name)
dataList.add(pos,i)
}
notifyItemChanged(pos)
}
}
override fun onFailure(call: Call<List<ModelClass>?>, t: Throwable) {
}
})
}
override fun getItemCount(): Int {
return dataList.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun getStock() {
binding.skuStock.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
for (i in 0 until RecyclerViewAdapter.ob.dataSelected.size){
if (editable.toString().trim()!=""){
var x= editable.toString().trim().toInt()
RecyclerViewAdapter.ob.dataSelected[adapterPosition].item_stock=x
//getting current itemstock before pushing update.
//assigning latest itemstock to the data for the update
}
}
}
})
}
fun updateStockDetail(){
binding.skuCode.addTextChangedListener(object : TextWatcher{
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
}
override fun afterTextChanged(editable: Editable) {
var x:String=""
var pos:Int=adapterPosition
for (i in 0 until RecyclerViewAdapter.ob.dataSelected.size){
if (editable.toString().trim()!=""){
x=editable.toString().trim()
//RecyclerViewAdapter.ob.dataSelected[adapterPosition].sku_code=x
println("$x in if")
}
}
//println(RecyclerViewAdapter.ob.dataSelected[adapterPosition].sku_code)
//getting edited text and calling the function to get updated details.
getUpdatedDetails(x,pos)
binding.skuStock.removeTextChangedListener(this)
}
})
}
}
}
If possible please review my code and let me know what are things i need to work on.
Note: ob.dataselected is a global variable from another recyclerview adapter.
Textchangedlistener i am talking about is in the fun updateStockDetail()
In the documentation for afterTextChanged we see the following:
This method is called to notify you that, somewhere within s, the text has been changed. It is legitimate to make further changes to s from this callback, but be careful not to get yourself into an infinite loop, because any changes you make will cause this method to be called again recursively.
(The emphasis is mine.) I don't see where you remove the text watcher before changing the text, so you may be in an infinite loop.
To remove the text watcher use removeTextChangedListener. You can replace the text watcher after making changes.
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 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
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))