How to pass a parameter to a extension function in Kotlin - android

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
}
}

Related

How to add filter to edit text for mentioning a user in group chat

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) {
}
})

(Android) Unable to show dot as thousand separator in edit text using a pattern

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

Android Livedata not updating EditText

I am using Android LiveData in 3 different EditText. I have to show the result of multiplying the values of the first two EditText into the third EditText. I took advantage of an advice given to me on this site, and actually the third value is updated with the result of the multiplication of the first two. The problem is that the update does not happen live, but only happens when I leave and re-enter the activity. I am attaching the XML file, the activity, and the viewmodel.
XML:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
tools:context=".MainActivity">
<EditText
android:id="#+id/num1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:backgroundTint="#color/white"
android:inputType="number" />
<EditText
android:id="#+id/num2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:backgroundTint="#color/white"
android:inputType="numberDecimal" />
<EditText
android:id="#+id/num3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:backgroundTint="#color/white"
android:inputType="numberDecimal" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Activity
class MainActivity: AppCompatActivity() {
private lateinit var binding: MainActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
binding = DataBindingUtil.setContentView(
this,
R.layout.main_activity
)
viewModel=
ViewModelProvider(this, factory)[MainActivityViewModel::class.java]
initView(binding)
}
private fun initView(
binding:
MainActivityBinding
) {
viewModel.num1.value = root?.num1?: 0
viewModel.num2.value = root?.num2?: 0.0
viewModel.num1.observe(lifecycleOwner, Observer { newNum1->
binding.num1.setText(
newNum1.toString()
)
})
viewModel.num2.observe(lifecycleOwner, Observer { newNum2->
binding.num2.setText(
newNum2.toString()
)
})
binding.num1.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
viewModel.num1.value =
binding.num1.text?.toString()?.toInt()
?: 0
}
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
binding.num2.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
viewModel.num2.value =
binding.num2.text?.toString()?.toDouble()
?: 0.0
}
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
fun <A, B> LiveData<A>.combineWith(b: LiveData<B>): LiveData<Pair<A?, B?>> =
MediatorLiveData<Pair<A?, B?>>().apply {
var lastA: A? = this#combineWith.value
var lastB: B? = b.value
addSource(this#combineWith) {
lastA = it
value = Pair(lastA, lastB)
}
addSource(b) {
lastB = it
value = Pair(lastA, lastB)
}
}
viewModel.num1.combineWith(viewModel.num2)
.observe(
this,
Observer { (first, second) ->
if (first != null && second != null) {
binding.num3.setText((first * second).toString())
}
}
)
}
binding.num1.isFocusableInTouchMode = true
binding.num2.isFocusableInTouchMode = true
binding.num3.isFocusableInTouchMode = true
}
}
ViewModel
class RapportiAltriCostiViewModel(private val repositoryDB: DbRepository) : ViewModel() {
var num1= MutableLiveData<Int>()
var num2= MutableLiveData<Double>()
}
Would anyone know how to solve?
Thank you for your patience and help!
UPDATE
I tried with TextWatcher but it goes in loop:
binding.num1.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
viewModel.num1.value =
binding.num1.text?.toString()?.toInt()
?: 0
}
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
binding.num2.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
viewModel.num2.value =
binding.num2.text?.toString()?.toDouble()
?: 0.0
}
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
And I can't remove the TextWatcher after assigning the value, as I read on another question on the site, because I need them to always listen.
Thanks for the patience once again!
Something similar to this. To avoid cyclic updates you may just compare new value inside onFirstChanged/onSecondChanged with value in your liveData and skip liveData.value = newValue in that way.
class MainActivity : AppCompatActivity() {
private lateinit var binding: MainActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
binding = DataBindingUtil.setContentView(
this,
R.layout.main_activity
)
viewModel =
ViewModelProvider(this, factory)[MainActivityViewModel::class.java]
initView(binding)
}
private fun initView(
binding:
MainActivityBinding
) {
binding.num1.listenChanges { viewModel.onFirstChanged(it) }
binding.num2.listenChanges { viewModel.onSecondChanged(it) }
viewModel.num1
.observe(
lifecycleOwner,
Observer { num1Value ->
binding.num1.setText(num1Value.toString())
}
)
viewModel.num2
.observe(
lifecycleOwner,
Observer { num2Value ->
binding.num2.setText(num2Value.toString())
}
)
viewModel.num3
.observe(
lifecycleOwner,
Observer { result ->
binding.num3.setText(result.toString())
}
)
}
binding.num1.isFocusableInTouchMode = true
binding.num2.isFocusableInTouchMode = true
binding.num3.isFocusableInTouchMode = true
}
private fun EditText.listenChanges(textChanged: (String) -> Unit) {
addTextChangedListener(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) {
textChanged(s.toString())
}
})
}
class RapportiAltriCostiViewModel(private val repositoryDB: DbRepository) : ViewModel() {
val num1 = MutableLiveData<Int>(0)
val num2 = MutableLiveData<Double>(0.0)
val num3: LiveData<Double>
get() = num1.combineWith(num2) { first, second ->
(first ?: 0) * (second ?: 0.0)
}
fun onFirstChanged(newValue: Int) {
if (num1.value != newValue) {
num1.value = newValue
}
}
fun onSecondChanged(newValue: Double) {
if (num2.value != newValue) {
num2.value = newValue
}
}
private companion object {
private fun <A, B, R> LiveData<A>.combineWith(b: LiveData<B>, combine: (A?, B?) -> R?): LiveData<R> =
MediatorLiveData<R>().apply {
var lastA: A? = this#combineWith.value
var lastB: B? = b.value
addSource(this#combineWith) {
lastA = it
value = combine.invoke(lastA, lastB)
}
addSource(b) {
lastB = it
value = combine.invoke(lastA, lastB)
}
}
}
}

How can i validate multiple editText fields and enable button?

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())
}

Convert my EditText Input to an observable stream

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)

Categories

Resources