To give a bit of context, I have a spinner and several EditText boxes. "Blankets" is the only editable EditText box. "Level" and "Blankets" can each be changed and should update all the others accordingly.
I've been unable to stop "Level" and "Blankets" from looping infinitely though.
I'm still fairly new to coding so I've been trying to keep things as simple as possible.
//Level Spinner
levelSpinner.adapter = levelArrayAdapter
levelSpinner.onItemSelectedListener = object :
AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
levelInt = Integer.parseInt(levelSpinner.selectedItem.toString())
println("LEVEL SPINNER VALUE IS " + levelInt)
calculateBlanketsbyLevel()
println("BLANKETS BY LEVEL = " + blanketsInt)
characterBlankets.setText("" + blanketsInt)
calculateTotalCPbyLevel()
println("CP BY LEVEL = " + cpInt)
characterCP.setText("" + cpInt)
calculateFreeCP()
characterFree.setText("" + freeInt)
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
//Blanket Box
characterBlankets.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun afterTextChanged(p0: Editable?) {
val characterBlanketsStr = characterBlankets.editableText.toString()
println("CHARACTER BLANKETS AFTER TEXT CHANGED IS " + characterBlanketsStr)
if (characterBlanketsStr != ""){
blanketsInt = Integer.parseInt(characterBlanketsStr)
calculateLevelbyBlankets()
println("LEVEL BY BLANKETS = " + levelInt)
levelSpinner.setSelection(levelInt)
calculateTotalCPbyLevel()
println("CP BY LEVEL = " + cpInt)
characterCP.setText("" + cpInt)
calculateFreeCP()
characterFree.setText("" + freeInt)
}
else {
}
}
})
Related
How can I add a textwatcher to the edit text which can detect the character "#" and suggest user name from a list that matches the characters after # ?
This is what I have tried but something is wrong with it:
binding.etMessage.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(editable: Editable?) {
if (editable?.contains("#") == true || editable?.contains("@") == true
) {
val mNames = arrayOf("Amit","Sneha","Nishi")
val mArrayAdapter = context?.let {
ArrayAdapter(
it,
android.R.layout.simple_list_item_1,
android.R.id.text1,
mNames
)
}
mArrayAdapter?.filter?.filter(editable.substring())
binding.listMentionSuggestions.adapter = mArrayAdapter
binding.listMentionSuggestions.show()
}
else{
binding.listMentionSuggestions.hide()
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(charSequence: CharSequence?, start: Int, before: Int, count: Int) {
}
})
i am trying to get a phone number from editText in my project. the problem i'm having;
1 -> the first digit should not be 0.
2 -> unfortunately but I can't delete spaces..
The number format I want to get; (555) 555 55 55
The stage my code has come to;
class PhoneNumberMask(val editText: EditText) : TextWatcher {
var phoneNumber: String = ""
var isRunning: Boolean = 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 (isRunning || s.length == 15) {
return
}
isRunning = true
phoneNumber = when (s.length) {
0 -> "test"
1 -> "($s"
4 -> "$s) "
9 -> "$s "
12 -> "$s "
else -> s.toString()
}
editText.setText(phoneNumber).also { editText.setSelection(phoneNumber.length) }
isRunning = false
}
}
I think your strategy is going to have troubles when the user moves the cursor to the middle of the text field, backspaces, or tries to paste text. What I would try is filtering to only digits each time it's changed and insert parentheses/spaces for the whole string of digits every time. Then put the cursor back where they had it. You can figure out where the cursor should be more easily from onTextChanged than from afterTextChanged.
Something like this:
class PhoneNumberMask(val editText: EditText) : TextWatcher {
private var isRunning: Boolean = false
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (isRunning) {
return
}
var cursorPosition = start + count
val digits = s.filter(Char::isDigit)
.dropWhile { it == '0' }
.take(10)
cursorPosition -= s.take(cursorPosition).run {
count { !it.isDigit() } + filter(Char::digit).takeWhile { it == '0' }.count()
}
val output = StringBuilder(digits)
fun punctuate(position: Int, punctuation: String) {
if (digits.length > position) {
output.insert(position, punctuation)
if (cursorPosition > position) {
cursorPosition += punctuation.length
}
}
}
punctuate(8, " ")
punctuate(6, " ")
punctuate(3, ") ")
punctuate(0, "(")
isRunning = true
editText.setText(output)
editText.setSelection(cursorPosition.coerceAtMost(output.length))
isRunning = false
}
override fun afterTextChanged(s: Editable) {
}
}
Here, I have to show currency decimal separator and thousands separators as per the given input like:
private var decimalSeparator: Char = '.'
private var thousandSeparator: String = ","
fun setDecimalSeparator(decimalSeparator: Char) {
this.decimalSeparator = decimalSeparator
}
fun setThousandSeparator(thousandSeparator: String) {
this.thousandSeparator = thousandSeparator
}
And I have added text watcher for the edit text field to add a respective decimal and thousands separator like this with the help of Add comma as thousands separator for numbers in EditText for Android Studio?
field.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
Log.d("CharCount:", p0.toString().length.toString())
field.removeTextChangedListener(this)
try {
var givenstring: String = p0.toString()
if (givenstring.contains(thousandSeparator)) {
givenstring = givenstring.replace(thousandSeparator.toRegex(), "")
}
val longVal: Long = givenstring.toLong()
val formatter = DecimalFormat("#$thousandSeparator###$thousandSeparator###")
val formattedString = formatter.format(longVal)
field.setText(formattedString)
field.setSelection(field.text.length)
// to place the cursor at the end of text
} catch (nfe: NumberFormatException) {
nfe.printStackTrace()
} catch (e: Exception) {
e.printStackTrace()
}
field.addTextChangedListener(this)
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
// no need any callback for this.
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
// no need any callback for this.
}
})
this is not working when thousandSeparator is period(.); can anyone help me with this?. Thanks in advance.
Here, this is how it worked out.
I took help from https://docs.oracle.com/javase/tutorial/i18n/format/decimalFormat.html and
Add comma as thousands separator for numbers in EditText for Android Studio?
field.addTextChangedListener(object : TextWatcher {
// https://stackify.dev/354994-add-comma-as-thousands-separator-for-numbers-in-edittext-for-android-studio
override fun afterTextChanged(p0: Editable?) {
field.removeTextChangedListener(this)
try {
var givenstring: String = p0.toString()
if (givenstring.contains(thousandSeparator)) {
givenstring = givenstring.replace(thousandSeparator.toString(), "")
}
val doubleVal: Double = givenstring.toDouble()
// https://docs.oracle.com/javase/tutorial/i18n/format/decimalFormat.html
val unusualSymbols = DecimalFormatSymbols()
unusualSymbols.decimalSeparator = decimalSeparator
unusualSymbols.groupingSeparator = thousandSeparator
val formatter = DecimalFormat("#,##0.##", unusualSymbols)
formatter.groupingSize = 3
val formattedString = formatter.format(doubleVal)
field.setText(formattedString)
field.setSelection(field.text.length)
// to place the cursor at the end of text
} catch (nfe: NumberFormatException) {
nfe.printStackTrace()
} catch (e: Exception) {
e.printStackTrace()
}
field.addTextChangedListener(this)
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
// no need any callback for this.
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
// no need any callback for this.
}
})
i have made custom text watcher that good for number or currency. You can get it here : https://github.com/zihadrizkyef/TextWatcherForMoney
I have an extension function in kotlin to check is it a valid string or not as stated below.
fun EditText.onAfterTextChanged(listener: (String) -> Unit) {
addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(editable: Editable?) {
val input = editable?.toString()
val allowedChars = context.getString(R.string.supported_digits)
val newValue = replaceInvalidCharacters(input, allowedChars)
if (newValue != input) {
setText(newValue)
setSelection(text.length)
}
listener(newValue)
}
override fun beforeTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
})
}
private fun replaceInvalidCharacters(value: String?, allowedChars: String): String {
var finalValue = value ?: ""
if (finalValue.isNotEmpty()) {
val lastChar = finalValue.last()
if (!allowedChars.contains(lastChar, false)) {
finalValue = finalValue.dropLast(1)
}
}
return finalValue
}
I am using it like:
editText.onAfterTextChanged {
val length = it.length
if (length >= 250) {
activity?.toast(getString(R.string.max_limit_reached))
return#onAfterTextChanged
}
}
Here I want to pass allowedChars as a parameter to this extension as there are different strings are there for different EditText's in the application. Like 1 EditText may allow only number's but not +,- and some edit text may allow only alphanumeric, etc. Is there any way to pass a parameter to the extension?
What you can do is update the extension function signature by adding a parameter before the callback function. So, it'll look something like this
fun EditText.onAfterTextChanged(allowedChars: String, listener: (String) -> Unit) {
addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(editable: Editable?) {
val input = editable?.toString()
val allowedChars = context.getString(R.string.supported_digits)
val newValue = replaceInvalidCharacters(input, allowedChars)
if (newValue != input) {
setText(newValue)
setSelection(text.length)
}
listener(newValue)
}
override fun beforeTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
})
}
And you can call it like so:
editText.onAfterTextChanged("123abc") {
val length = it.length
if (length >= 250) {
activity?.toast(getString(R.string.max_limit_reached))
return#onAfterTextChanged
}
}
So i have been trying to convert my EditText input that i get from my TextWatcher to an observable of stream but i cannot convert it.
I am trying the following
etSearch.addTextChangedListener(object: TextWatcher{
override fun afterTextChanged(p0: Editable?) {
//I want to create an observable here to send events
Observable.create(e->e.next(p0));
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
})
I am not able to figure out where i should create my events so that i am able to subscribe to it.
You can simply create an extension in kotlin which returns a Flowable of EditTextFlow
fun EditText.addTextWatcher(): Flowable<EditTextFlow> {
return Flowable.create<EditTextFlow>({ emitter ->
addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
emitter.onNext(EditTextFlow(p0.toString(), EditTextFlow.Type.BEFORE))
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
emitter.onNext(EditTextFlow(p0.toString(), EditTextFlow.Type.ON))
}
override fun afterTextChanged(p0: Editable?) {
emitter.onNext(EditTextFlow(p0.toString(), EditTextFlow.Type.AFTER))
}
})
}, BackpressureStrategy.BUFFER)
}
EditTextFlow
data class EditTextFlow(
val query: String,
val type: Type
) {
enum class Type { BEFORE, AFTER, ON }
}
Then use it like this:
etSearch.addTextWatcher()
.filter { it.type == EditTextFlow.Type.AFTER }
.map { it.query }
.flatMap { /*Make any request or anything*/ }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = {
// Update UI here
},
onError = {
// Log error
}
)
Actually, there is a library for this.
You can use it as
RxTextView.textChanges(etSearch)