I'm having a difficult time trying how to figure out how to create a callback in Kotlin using lambdas. I have a custom TextInputEditText and I want to implement a function that the activity can call when text changes.
Here is my custom EditText:
class EditTextEx : TextInputEditText, TextWatcher {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
// Call the callback onTextAvailable with the EditText's text (s.toString)
}
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
}
In my activity I want to have a callback that gets called when the onTextChanged event gets called. The callback in the custom control sends only the text back to the client. So in my activity, I want something like this:
editText.onTextAvailable(text -> do something )
It's actually quite easy to do, look:
inline fun EditText.onTextChanged(crossinline onTextChange: (String) -> Unit): TextWatcher {
val textWatcher = object: TextWatcher {
override fun afterTextChanged(editable: Editable) {
onTextChange(editable.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
}
this.addTextChangeListener(textWatcher)
return textWatcher
}
Now you can call
editText.onTextChanged { text -> /* do something */ }
In addition to the solution by #EpicPandaForce, there are a couple other solutions. If you want to stick with using a class as you've shown in your example, then you can do this:
class EditTextEx : TextInputEditText, TextWatcher {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private var mOnTextWatcherCallback: (m: String) -> Unit = {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (mOnTextWatcherCallback != null)
mOnTextWatcherCallback(s.toString())
}
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
fun onTextChange(callback: (text: String) -> Unit) {
mOnTextWatcherCallback = callback
}
}
Then in your activity create a function:
fun onTextChange(text: String) {
// Do something with the text.
}
And then setup your callback as follows:
my_edittext.onTextChange(::onTextChange)
This solution allows you to re-use the same onTextChange function for other controls that want to use it as well.
If you prefer to use an interface to define the callback, do this:
class EditTextEx : TextInputEditText, TextWatcher {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private var mOnTextWatcherCallback: ITextWatcher? = null
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (mOnTextWatcherCallback != null)
mOnTextWatcherCallback!!.onTextChanged(s.toString())
}
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
fun onTextChange(callback: ITextWatcher) {
mOnTextWatcherCallback = callback
}
}
Then in your activity, create the callback as follows:
val textChangeHandler = object: ITextWatcher {
override fun onTextChanged(text: String) {
var t = text
}
}
And then setup your callback for your edittext controls as follows:
my_edittext.onTextChange(textChangeHandler)
Try something like this:
fun getEditTextChange(editText: EditText, onTextChange: (String) -> Unit){
val tw = object: TextWatcher {
private var region = Locale.getDefault().language
override fun afterTextChanged(s: Editable?) {
onTextChange.invoke(s.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
}
editText.addTextChangedListener(tw)
}
Hope it helps
Related
I assume I'm missing something simple, but I can't seem to get joystick actions (X,Y,Z,RZ) from a game pad using View.OnGenericMotionListener in Android API 30. I followed an example (I thought) but joystick motions are undetected. If I replace OnGenericMotionListener and my onGenericMotion functions with OnTouchListener and onTouch I see exactly what I would expect from touches of the touchpad. In the code below I have reduced to a minimum to see the issue. With joystick = BasicMotion I do not get my debug log messages- I only get
I/ViewRootImpl#cc64931[MainActivity]: ViewPostIme key 1
If I change to joystick = BasicTouch I get my debug log.
What am I missing to get input from a game pad? main_activity and BasicMotion/BasicTouch classes below. Also note if I add the onGenericMotionEvent function to MainActivity, then I see joystick inputs there, so I know the game pad is working. Code is in Kotlin, but I'd be happy with a java solution.
class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
// val joystick = BasicMotion(this)
val joystick = BasicTouch(this)
setContentView(joystick)
super.onCreate(savedInstanceState)
}
}
BasicMotion class:
class BasicMotion : SurfaceView, SurfaceHolder.Callback, View.OnGenericMotionListener {
constructor(context: Context?) : super(context) {
holder.addCallback(this)
setOnGenericMotionListener(this)
}
constructor(context: Context?, attributes: AttributeSet?) : super(context, attributes) {
holder.addCallback(this)
setOnGenericMotionListener(this)
}
constructor(context: Context?, attributes: AttributeSet?, style: Int) : super(
context, attributes, style
) {
holder.addCallback(this)
setOnGenericMotionListener(this)
}
override fun surfaceCreated(holder: SurfaceHolder) {}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
override fun surfaceDestroyed(holder: SurfaceHolder) {}
override fun onGenericMotion(v: View, event: MotionEvent): Boolean {
Log.d("Debug Motion", "detected")
return true
}
}
Equivalent BasicTouch class:
class BasicTouch : SurfaceView, SurfaceHolder.Callback, View.OnTouchListener {
constructor(context: Context?) : super(context) {
holder.addCallback(this)
setOnTouchListener(this)
}
constructor(context: Context?, attributes: AttributeSet?) : super(context, attributes) {
holder.addCallback(this)
setOnTouchListener(this)
}
constructor(context: Context?, attributes: AttributeSet?, style: Int) : super(
context, attributes, style
) {
holder.addCallback(this)
setOnTouchListener(this)
}
override fun surfaceCreated(holder: SurfaceHolder) {}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
override fun surfaceDestroyed(holder: SurfaceHolder) {}
override fun onTouch(v: View, event: MotionEvent): Boolean {
Log.d("Debug Touch", "detected")
return true
}
}
My aim is to automatically display the numbers entered by the user in the textView below using 2 edittext as shown below. How can I do this?Because this is crush. I would appreciate it if you explain in detail. Thanks in advance.
items3=pronumber.text.toString() // Edittext
items4=proprice.text.toString()// Edittext
pronumber.addTextChangedListener ( object:TextWatcher{
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
TODO("Not yet implemented")
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
totalprice.text= "Toplam Tutar : "+(items3.toInt()*items4.toInt()).toString() +" TL" //TextView
}
} )
proprice.addTextChangedListener ( object:TextWatcher{
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
TODO("Not yet implemented")
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
totalprice.text= "Toplam Tutar : "+(items3.toInt()*items4.toInt()).toString() +" TL" //TextView
}
} )
items5=totalprice.text.toString()
You should change your code like
items3=pronumber.text.toString() // Edittext
items4=proprice.text.toString()// Edittext
pronumber.addTextChangedListener ( object:TextWatcher{
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
val first = pronumber.text.toString()
val second = proprice.text.toString()
totalprice.text = if (first.isBlank() || second.isBlank()){
"Toplam Tutar : 0 TL"
} else {
"Toplam Tutar : ${first.toInt().times(second.toInt())} TL"
}
}
} )
proprice.addTextChangedListener ( object:TextWatcher{
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
totalprice.text = if (first.isBlank() || second.isBlank()){
"Toplam Tutar : 0 TL"
} else {
"Toplam Tutar : ${first.toInt().times(second.toInt())} TL"
}
}
} )
items5=totalprice.text.toString()
In my Recyclerview's Adapter class, I have done something as below:
holder.mEdtDescription.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
listener.onDeleteOrDescribe(position, "" + holder.mEdtDescription.text.toString(), "describe")
}
})
holder.mIvDelete.setOnClickListener {
listener.onDeleteOrDescribe(position, "", "delete")
}
mEdtDescription is EditText.
On deleting some value from the recycler my adapter calls method onDeleteOrDescribe.
Unfortunately, At the same time onTextChanged is also calling.
How can I avoid calling onTextChanged when I am deleting soemthing?
Hope you got the point.
As per the solution, I have tried disabling textWatcher as below :
val textWatcher: TextWatcher = object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
ToastUtil.displayShortDurationToast(mContext,"called")
listener.onDeleteOrDescribe(
position,
"" + holder.mEdtDescription.text.toString(),
"describe"
)
}
}
holder.mEdtDescription.addTextChangedListener(textWatcher)
holder.mIvDelete.setOnClickListener {
//listener.onDeleteStep(position)
holder.mEdtDescription.removeTextChangedListener(textWatcher)
listener.onDeleteOrDescribe(position, "", "delete")
holder.mEdtDescription.addTextChangedListener(textWatcher)
}
But, still is calling onTextChange() :(
You could try disabling the TextWatcher temporarily:
val textWatcher : TextWatcher = object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
listener.onDeleteOrDescribe(position, holder.mEdtDescription.text.toString(), "describe")
}
}
holder.mEdtDescription.addTextChangedListener(textWatcher)
holder.mIvDelete.setOnClickListener {
holder.mEdtDescription.removeTextChangedListener(textWatcher)
listener.onDeleteOrDescribe(position, "", "delete")
holder.mEdtDescription.addTextChangedListener(textWatcher)
}
I can't call function of my class from object inside this class.
How should i do this ?
class LoginActivity: AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
}
private fun disableLoginButton(){
button_login.isEnabled = false
}
private object textChangeListener: TextWatcher{
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
//Here i cannot call function
disableLoginButton() // unresolved reference.
}
}
}
But when i call LoginActivity().disableLoginButton() instead disableLoginButton()it's visible, but fails with
NullPointerException
on login_button
Try this :
class LoginActivity: AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
editTextSample.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) {
disableLoginButton()
}
})
}
}
private fun disableLoginButton(){
button_login.isEnabled = false
}
EDIT: Doesn't work
From jetbrains team:
In Java and Kotlin, "inner" means "capturing the outer instance", where as "nested" means simply declared inside something else. Java's static classes are only nested, non-static nested classes are inner. In Kotlin you have to explicitly declare something as "inner" (we reversed the Java's convention). So, your object is not inner, btu only nested. And no named object can be inner, in fact: named objects are singletons, so the can not depend on any kind of outer instance.
Try specifying the object as inner:
private inner object textChangeListener: TextWatcher{
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int {
//Here i cannot call function
disableLoginButton() // unresolved reference.
}
}
This should allow you to access the outer scope.
I want to use a centered spinner where the width of the spinner is only as wide as the selected item text. From my research it seems that this is not natively supported out of the box with an attribute so I found another StackOverflow question/answer and tried implementing that but ran into some issues with it.
So I took option 1 from this SO response and implemented it in Kotlin and It's not working for me
class DynamicWidthSpinner #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatSpinner(context, attrs, defStyleAttr) {
override fun setAdapter(adapter: SpinnerAdapter?) {
super.setAdapter(if (adapter != null) WrapperSpinnerAdapter(adapter) else null)
}
inner class WrapperSpinnerAdapter(val baseAdapter: SpinnerAdapter) : SpinnerAdapter {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return baseAdapter.getView(selectedItemPosition, convertView, parent)
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return baseAdapter.getDropDownView(position, convertView, parent)
}
override fun getCount(): Int = baseAdapter.count
override fun getItem(position: Int): Any = baseAdapter.getItem(position)
override fun getItemId(position: Int): Long = baseAdapter.getItemId(position)
override fun getItemViewType(position: Int): Int = baseAdapter.getItemViewType(position)
override fun getViewTypeCount(): Int = baseAdapter.viewTypeCount
override fun hasStableIds(): Boolean = baseAdapter.hasStableIds()
override fun isEmpty(): Boolean = baseAdapter.isEmpty
override fun registerDataSetObserver(observer: DataSetObserver) {
baseAdapter.registerDataSetObserver(observer)
}
override fun unregisterDataSetObserver(observer: DataSetObserver) {
baseAdapter.unregisterDataSetObserver(observer)
}
}
}
and in my MainActivity I'm doing this from onCreate
val spinner: DynamicWidthSpinner = findViewById(R.id.global_toolbar_location_spinner)
val tempLocationList = ArrayList<String>()
tempLocationList.add("Test1")
tempLocationList.add("Much longer test string 2")
spinner.adapter = ArrayAdapter(
this,
R.layout.global_toolbar_spinner_item,
tempLocationList
)
spinner.onItemSelectedListener = object : OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, arg3: Long) {
// TODO: do stuff on selection here
}
override fun onNothingSelected(arg0: AdapterView<*>) {
// TODO: do nothing... yet
}
}
spinner.setSelection(0)
and I am using my custom Spinner in the layout xml (ommitting everything else that is not necessary because I am able to get it work just fine using the native <Spinner> or androidx compat Spinner
<com.blablabla.app.ui.DynamicWidthSpinner
android:id="#+id/global_toolbar_location_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:spinnerMode="dialog"
/>
What I see is just the first item "Test1" and nothing happens when I tap on it and arrow seems to have disappeared now as well
I figured out the issue. Turns out that this is one of those cases where the #JvmOverloads doesn't work. Once I converted it to the multiple constructor kotlin syntax it worked without a problem
class DynamicWidthSpinner : AppCompatSpinner {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
...
}