I am trying to figure out how to validate 6 EditText input fields and enable button
button_step_one_next_FSF.isEnabled = true
when everything fits my condition. I want to validate everything using this util class without creating TextWatcher object.
Here is my editText util class
inline fun EditText.onTextChange(crossinline f: (s: CharSequence?) -> Unit) {
val listener = object : TextWatcher {
override fun onTextChanged(s: CharSequence, start: Int,
before: Int, count: Int) {
f(s)
}
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
}
addTextChangedListener(listener)
}
Here is short validation method example
private fun validateInput() {
edit_text_name.onTextChange { s ->
val name: String = s?.toString() ?: ""
if (!name.isNameNotValid()) {
text_input_name.isEndIconVisible = true
text_input_name.isErrorEnabled = false
} else {
text_input_name.error = getString(R.string.error_not_valid_name)
text_input_name.isEndIconVisible = false
}
}
edit_text_surname.onTextChange { s ->
val surname: String = s?.toString() ?: ""
if (!surname.isNameNotValid()) {
text_input_surname.isEndIconVisible = true
text_input_surname.isErrorEnabled = false
} else {
text_input_surname.error = getString(R.string.error_not_valid_surname)
text_input_surname.isEndIconVisible = false
}
}
I just added this method checkButtonEnableState() at the end of each validation in TextWatcher lambda expression and it solved my problem!
private fun checkButtonEnableState() {
button_step_one_next_FSF.isEnabled =
(!edit_text_name.text.toString().isNameNotValid()
&& !edit_text_surname.text.toString().isNameNotValid()
&& edit_text_password_FSF.text.toString().isValidPassword()
&& edit_text_password_confirm_FSF.text.toString().isValidPassword()) &&
(edit_text_password_confirm_FSF.text.toString() == edit_text_password_FSF.text.toString())
}
Related
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) {
}
}
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
}
}
This is my GenericTextEnterPin Class
class GenericTextEnterPinPassword (private val view: View, private val editText: ArrayList<EditText>) : TextWatcher {
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?) {
val string = s.toString()
when (view.id) {
R.id.otp_edit_box5 -> {
if (string.length == 1) editText[1].requestFocus()
}
R.id.otp_edit_box6 -> {
if (string.length == 1) editText[2].requestFocus() else if (string.isEmpty()) editText[0].requestFocus()
}
R.id.otp_edit_box7 -> {
if (string.length == 1) editText[3].requestFocus() else if (string.isEmpty()) editText[1].requestFocus()
}
R.id.otp_edit_box8 -> {
if (string.isEmpty()) editText[2].requestFocus()
// here we are getting 4 size i want sethere and want to get callback in another class
}
}
}
}
i want to getCall Back in my widget class where we are displaying some other view but i am not getting how to get call back in another class below is my class where i want to call back .
class AppLockWidgetImpl #Inject constructor(
private val context: Context,
override val onClicked: SingleLiveData<CallToAction>
) : AppLockWidget {
}
You can pass your callback as a lambda function to GenericTextEnterPinPassword.
class GenericTextEnterPinPassword (
private val view: View,
private val editText: ArrayList<EditText>,
private val callback: () -> Unit
): TextWatcher {
// ...
R.id.otp_edit_box8 -> {
if (string.isEmpty()) editText[2].requestFocus()
callback()
}
// ...
}
Usage:
val textWatcher = GenericTextConfirmPassword(otp_edit_box11, edit) {
// Whatever you wish to do upon callback
}
otp_edit_box11.addTextChangedListener(textWatcher)
I have this error that comes up when the user inputs a wrong password or a password that doesn't meet the requirements. The thing is, when the user writes the right password, the error is still there even if the user can login so how can I turn it off when the password is right?
This is the part I'm talking about:
loginViewModel.loginFormState.observe(this, Observer {
val loginState = it ?: return#Observer
binding.btnLogin.isEnabled = loginState.isDataValid
if (loginState.emailError != null) {
binding.etEmail.error = getString(loginState.emailError)
}
if (loginState.passwordError != null) {
// this is the error i need to turn off
binding.tfPassword.error = getString(loginState.passwordError)
}
})
binding.etEmail.afterTextChanged {
loginViewModel.loginDataChanged(
binding.etEmail.text.toString(),
binding.etPassword.text.toString()
)
}
binding.etPassword.afterTextChanged {
loginViewModel.loginDataChanged(
binding.etEmail.text.toString(),
binding.etPassword.text.toString()
)
}
}
private fun TextInputEditText.afterTextChanged(afterTextChanged: (String) -> Unit) {
this.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(editable: Editable?) {
afterTextChanged.invoke(editable.toString())
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
})
}
}
To get rid of error in TextInputEditText you need to pass null to error parameter. I see you do not handle a state in which you pass a correct password. In your case
if (loginState.passwordError != null) {
binding.tfPassword.error = getString(loginState.passwordError)
} else { // you are missing this else statement
binding.tfPassword.error = null
}
or even prettier
binding.tfPassword.error = loginState.passwordError?.getString(loginState.passwordError)
which will assign null to tfPassword.error when passwordError will be null. Of course this will also apply to etEmail.error
I have some extension functions function below.
fun EditText.setEmailValidationListener(): TextWatcher {
val textWatcher = object : TextWatcher {
override fun beforeTextChanged(text: CharSequence?, start: Int, count: Int, after: Int) { }
override fun onTextChanged(text: CharSequence?, start: Int, before: Int, count: Int) { }
override fun afterTextChanged(text: Editable?) { validateEmail() }
private fun validateEmail(): Boolean {
if (validateEmailFormat(showError = false)) {
getParentInputLayout()?.isErrorEnabled = false
return true
}
return false
}
}
addTextChangedListener(textWatcher)
return textWatcher
}
fun EditText.setPasswordValidationListener(): TextWatcher {
val textWatcher = object : TextWatcher {
override fun beforeTextChanged(text: CharSequence?, start: Int, count: Int, after: Int) { }
override fun onTextChanged(text: CharSequence?, start: Int, before: Int, count: Int) { }
override fun afterTextChanged(text: Editable?) { validateEmpty() }
private fun validatePasswordText(): Boolean {
if (validateEmptyText(showError = false)) {
getParentInputLayout()?.isErrorEnabled = false
return true
}
return false
}
}
addTextChangedListener(textWatcher)
return textWatcher
}
fun EditText.validateEmailFormat(showError: Boolean = true): Boolean
{
// Do something checking the Email
return false
}
fun EditText.validatePasswordText(showError: Boolean = true): Boolean
{
// Do something checking the Password
return false
}
private fun EditText.getParentInputLayout(): TextInputLayout? {
if (parent is TextInputLayout) {
return parent as TextInputLayout
}
return null
}
Both setEmailValidationListener and setPasswordValidationListener are identical, except for the validation function they use respectively i.e. validateEmailFormat and validatePasswordFormat.
So I plan to refactor the two function common code into a common function as below
fun EditText.setupTextChangeListener(validatorFunc : (showError: Boolean) -> Boolean): TextWatcher {
val textWatcher = object : TextWatcher {
override fun beforeTextChanged(text: CharSequence?, start: Int, count: Int, after: Int) { }
override fun onTextChanged(text: CharSequence?, start: Int, before: Int, count: Int) { }
override fun afterTextChanged(text: Editable?) { validateEmpty() }
private fun validateEmpty(): Boolean {
if (validatorFunc(false)) {
getParentInputLayout()?.isErrorEnabled = false
return true
}
return false
}
}
addTextChangedListener(textWatcher)
return textWatcher
}
... where it basically just to send in validationFunc as parameter to it.
However, I can't find any way of sending the EditText.validateEmailFormat and EditText.validatePasswordFormat into the validationFunc function parameter.
How could I achieve that?
Some theory
Signature of extension functions is bit more complicated than in may look at first. The extension needs to have some reference to object of this class to be able to act upon it.
In fact the extension method
fun EditText.validateEmailFormat(showError: Boolean = true): Boolean
after decompiling to plain old java, looks like this:
public static final boolean validateEmailFormat(#NotNull EditText $receiver, boolean showError)
As it's (almost) impossible to change already-compiled Java class. So Kotlin (and quite possibly other languages that have concept of extension methods) uses static methods, with first parameter being receiver of extending class, to make it work.
Back to the business
Your validateEmailFormat is in fact of type EditText.(Boolean) -> Boolean and at the same time is of type (EditText, Boolean) -> Boolean. So you need to do either of two things:
First you can make EditText.setupTextChangeListener accept validatorFunc as EditText.(Boolean) -> Boolean or (EditText, Boolean) -> Boolean instead of (Boolean) -> Boolean.
Or you refrain from extending EditText in fun EditText.validateEmailFormat(Boolean) and make it plain Kotlin function, e.g. something like this fun validateEmailFormat(String, Boolean).
As you are extensively using extension functions, I assume the first option is correct solution for you.
fun EditText.validateEmailFormat() can be passed as EditText::validateEmailFormat.