I am trying to use the done button of the soft keyboard to activate a method via databinding. Just like onClick. Is there a way to do that?
example:
<EditText
android:id="#+id/preSignUpPg2EnterPhone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
onOkInSoftKeyboard="#{(v) -> viewModel.someMethod()}"
/>
onOkInSoftKeyboard doesn't exists... Is there something to create this behavior?
Thanks!
I won't claim to be an expert in onEditorAction() or soft keyboard. That said, assuming you use the solution to the stack overflow question Firoz Memon suggested, you can make it happen. Even if there is another solution that works better, this can give you an idea on how to add your own event handlers.
You'd need a binding adapter that takes some kind of handler. Let's assume you have an empty listener like this:
public class OnOkInSoftKeyboardListener {
void onOkInSoftKeyboard();
}
Then you need a BindingAdapter:
#BindingAdapter("onOkInSoftKeyboard") // I like it to match the listener method name
public static void setOnOkInSoftKeyboardListener(TextView view,
final OnOkInSoftKeyboardListener listener) {
if (listener == null) {
view.setOnEditorActionListener(null);
} else {
view.setOnEditorActionListener(new OnEditorActionListener() {
#Override
public void onEditorAction(TextView v, int actionId, KeyEvent event) {
// ... solution to receiving event
if (somethingOrOther) {
listener.onOkInSoftKeyboard();
}
}
});
}
}
Using Kotlin, kapt produces:
e: [kapt] An exception occurred: android.databinding.tool.util.LoggedErrorException: Found data binding errors.
****/ data binding error ****msg:Listener class kotlin.jvm.functions.Function1 with method invoke did not match signature of any method viewModel::signIn
(because viewModel::signIn is of type KFunction1) so we can't use a method reference. However, if we create a variable within the viewModel that is explicit about the type, then we can pass that variable as the binding's param. (or just use a class)
Bindings.kt:
#BindingAdapter("onEditorEnterAction")
fun EditText.onEditorEnterAction(f: Function1<String, Unit>?) {
if (f == null) setOnEditorActionListener(null)
else setOnEditorActionListener { v, actionId, event ->
val imeAction = when (actionId) {
EditorInfo.IME_ACTION_DONE,
EditorInfo.IME_ACTION_SEND,
EditorInfo.IME_ACTION_GO -> true
else -> false
}
val keydownEvent = event?.keyCode == KeyEvent.KEYCODE_ENTER
&& event.action == KeyEvent.ACTION_DOWN
if (imeAction or keydownEvent)
true.also { f(v.editableText.toString()) }
else false
}
}
MyViewModel.kt:
fun signIn(password: String) {
Toast.makeText(context, password, Toast.LENGTH_SHORT).show()
}
val signIn: Function1<String, Unit> = this::signIn
layout.xml:
<EditText
android:id="#+id/password"
app:onEditorEnterAction="#{viewModel.signIn}"
android:imeOptions="actionDone|actionSend|actionGo"
android:singleLine="true"/>
Just as i was looking at this myself, here a simpler version where the function is directly called from the data binding:
In your ViewModel use this function:
public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
return false; // if you want the default action of the actionNext or so on
return true; // if you want to intercept
}
And in the layout:
android:onEditorAction="#{(view,actionId,event) -> viewModel.onEditorAction(view,actionId,event)}"
Kotlin, without writing custom binding adapter
In Layout,
<EditText
...
android:onEditorAction="#{(view, actionId, event) -> viewModel.onDoneClicked(view, actionId, event)}" />
ViewModel
fun onDoneClicked(view: View, actionId: Int, event: KeyEvent?): Boolean {
if(actionId == EditorInfo.IME_ACTION_DONE) {
// handle here
return true
}
return false
}
Note: event can be null, so make KeyEvent nullable by putting ? there.
You can directly call login method what inside ViewModel by implementing setOnEditorActionListener to the Edittext, taking reference from binging class
loginFragmentBinding.etPassword.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
loginViewModel.login()
return#OnEditorActionListener true
}
false
})
Android framework already have this implemented. Take a look at TextViewBindingAdapter
You'll see those attributes, the documentation kind of glosses over what this means, but in a nutshell:
attribute = when this attribute appears in a layout file
type = then look for the implementation in this class
method = of a method with this name in the class defined in type
For more on this take a look at this blog post.
Related
I have a Bottom Sheet Dialog Fragment which contains four Fragment with ViewPager.
I want to call a method when onBackPressed clicked in Bottom Sheet Dialog Fragment. Implemented OnBackPressedCallback in my OnCreateView but it is not triggered. Any one have a idea why it is not called?
val callback = object : OnBackPressedCallback(true */ true means that the callback is enabled /*) {
override fun handleOnBackPressed() {
// Show your dialog and handle navigation
LogUtils.d("Bottom Sheet -> Fragment BackPressed Invoked")
}
}
// note that you could enable/disable the callback here as well by setting callback.isEnabled = true/false
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
I found this thread while looking for a solution to the same problem that exists in DialogFragment. The answers are in the comments above, but for completeness here is the information aggregated:
Solution
In your DialogFragment override onCreateDialog and set an OnKeyListener:
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
setOnKeyListener { _: DialogInterface, keyCode: Int, keyEvent: KeyEvent ->
if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.action == KeyEvent.ACTION_UP) {
// <-- Your onBackPressed logic here -->
return#setOnKeyListener true
}
return#setOnKeyListener false
}
}
}
Explanation
From an issue raised against requireActivity().onBackPressedDispatcher.addCallback not working for DialogFragments (https://issuetracker.google.com/issues/149173280):
Dialogs are separate windows that always sit above your activity's window. This means that the dialog will continue to intercept the system back button no matter what state the underlying FragmentManager is in, or what code you run in your Activity's onBackPressed() - which is where the OnBackPressedDispatcher plugs into.
Essentially the onBackPressedDispatcher is the wrong tool for the job when using any component that utilises Dialogs because of how they behave within an Application and exist outside (on top) of Activities.
#ITJscott has explained very well.
in case any one struggling in understanding/ implementing kotlin code here is JAVA code snippet for the same.
#NonNull
#Override
public Dialog onCreateDialog(#Nullable Bundle savedInstanceState) {
Dialog mDialog = super.onCreateDialog(savedInstanceState);
mDialog.setOnKeyListener((dialog, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
// <-- Your onBackPressed logic here -->
requireActivity().onBackPressed();
return true;
}
return false;
});
return mDialog;
}
This behaviour can also occur if you've set bottom sheet to be non-cancelable using .
So, to avoid this, you can use below code which detects certain events like keypad entry or back press. If you want to perform other action on other events, you can add the code here.
bottomSheetDialog.setOnKeyListener { _, keyCode, _ ->
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed()
return#setOnKeyListener true
} else {
return#setOnKeyListener false
}
}
The following are two blocks of code that should essentially do the same thing. But the second one does not execute the onEditorAction while the first one does. What is it about the second one that is different that prevents it from executing the code? NOTE: Only one of these is present in the code and not both.
// This one works
this.setOnEditorActionListener { v, actionId, event ->
if(actionId == EditorInfo.IME_ACTION_SEARCH){
mOnRunSearchCallback()
true
} else {
false
}
}
// This one does not work
this.setOnEditorActionListener(object : TextView.OnEditorActionListener {
override fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent): Boolean {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
mOnRunSearchCallback()
return true
}
return false
}
})
Change the second example with this
this.setOnEditorActionListener(object : TextView.OnEditorActionListener {
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
return true;
}
})
Basically, you have wrong arguments type for v and event both v & event are nullable.
I have Extension of EditText where I'm listenig to events from keyboard of EditText. I need to know when user press any button for showing (or not showing) error. So I make Observable for keys (rxbinding2) and I'm getting any press but when I press back button and cursor still in this EditText method onBackPressed doesn`t work.
How to filter onBack pressed?
fun EditText.changeWithFormatting(formatter: (String) -> String): Observable<String> {
return Observable.merge(
afterTextChangeEvents()
.map { editableText },
keys()
.filter { it.action == KeyEvent.ACTION_UP }
.map { editableText }
)
.map { changeText(formatter(it.toString())) }
}
Just listen for the keydown event. This should trigger before it gets handed to your UI element. super will pass it to the parent to go normal flow. I can't remember if it's return true or false, but I think it's true for handled.
#Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if ((keyCode == KeyEvent.KEYCODE_BACK))
{
onBackPressed();
return true;
}
return super.onKeyDown(keyCode, event);
}
How to override performClick in Kotlin to avoid warning?
next.setOnTouchListener(View.OnTouchListener { view, motionEvent ->
when (motionEvent.action){
MotionEvent.ACTION_DOWN -> {
val icon: Drawable = ContextCompat.getDrawable(activity.applicationContext, R.drawable.layer_bt_next)
icon.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY)
next.setImageDrawable(icon)
}
MotionEvent.ACTION_UP -> {
//view.performClick()
next.setImageResource(R.drawable.layer_bt_next)
}
}
return#OnTouchListener true
})
view.performClick does not work.
Try this way :
next.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> //Do Something
}
return v?.onTouchEvent(event) ?: true
}
})
Okay, I solved my own problem by overriding the OnTouch listener.
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
when (view) {
next -> {
Log.d("next", "yeyy")
when (motionEvent.action){
MotionEvent.ACTION_DOWN -> {
val icon: Drawable = ContextCompat.getDrawable(activity.applicationContext, R.drawable.layer_bt_next)
icon.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY)
next.setImageDrawable(icon)
}
MotionEvent.ACTION_UP -> {
view.performClick()
next.setImageResource(R.drawable.layer_bt_next)
}
}
}
previous -> {
//ingredients here XD
}
}
return true
}
And in that way, I can call single onTouch and implement it to many button and also can use the onClick by :
view.performClick()
Don't forget to implement :
View.OnTouchListener
And set the listener :
next.setOnTouchListener(this)
previous.setOnTouchListener(this)
I don't think your solution will actually solve them problem presented by the warning. The warning states that certain accessibility functions use performClick() to activate buttons. If you look in the View class, the performClick() funtions calls the onClickListener directly, meaning the code in the onTouchListener will not be executed (next.setImageResource(R.drawable.layer_bt_next)) for these accessibility functions, since the view will never be physically touched, and thus your onTouch code won't run. You have to do one of either:
Subclass the view you are setting the onTouchListener on, and override performClick to execute the code, or
Set an onClickListener on the view that executes the code.
You could just implement onClickListener in your onTouchListener class and manually call onClick() from your onTouchListener (where you have view.performClick() now), and then move your executable code to the onClick override. You would also have to set BOTH onTouchListener and onClickListener on your views.
I'm not sure this is the same issue you saw, but since I found this page searching for my issue, I thought I'd add my experience to help others :)
In my case the warning was being generated because the nullable view could have been of type Void. Calling the following:
nullableView?.setOnTouchListener(this)
produced the error:
Custom view Void has setOnTouchListener called on it but does not override performClick
Performing a null check and casting to a View before setting the listener solved for me in this case, since View will override performClick:
if (nullableView != null) (nullableView as View).setOnTouchListener(this)
After a ton of digging, and not being able to fix my variation of this issue with anything in this thread, I finally found a fix. Maybe it will work for some of you. I had this widget listener setter in my MainActivity onCreate function:
findViewById<TextView>(R.id.tvAnimalList).setOnTouchListener { v, event ->
mGestureDetector.onTouchEvent(event)
}
Which results in the warnings:
'onTouch' lambda should call 'View#performClick' when a click is detected
Custom view "TextView" has 'setOnTouchListener' called on it but does not override 'performClick'
First, I added a call to v.performClick(), which got rid of the first warning. Like this:
findViewById<TextView>(R.id.tvAnimalList).setOnTouchListener { v, event ->
v.performClick()
mGestureDetector.onTouchEvent(event)
}
I got rid of the second warning by changing the findViewById cast from <TextView> to <View>. Here's my warning-free result:
findViewById<View>(R.id.tvAnimalList).setOnTouchListener { v, event ->
v.performClick()
mGestureDetector.onTouchEvent(event)
}
private fun closeKeyboard(binding: ContollerMeterBinding) {
binding.scrollView.apply {
setOnTouchListener(OnTouchListener { v, event ->
if (event != null && event.action == MotionEvent.ACTION_MOVE) {
val imm =
activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
val isKeyboardUp = imm.isAcceptingText
if (isKeyboardUp) {
imm.hideSoftInputFromWindow(v.windowToken, 0)
}
}
performClick()
false
})
}
}
This works for me: (not directly related to onTouch event but yields the same warning, might be helpful to someone)
takePhotoButton.setOnTouchListener { _, motionEvent ->
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
//when user touch down
}
MotionEvent.ACTION_UP -> {
//when user touch release
}
}
true
}
I am working with this Kotlin function. I know that we have a function called mPasswordView!!.setOnEditorActionListener, that take parameter TextView.OnEditorActionListener, but what's that after it? we have curly brackets inside parameter?
mPasswordView!!.setOnEditorActionListener(TextView.OnEditorActionListener { textView, id, keyEvent ->
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin()
return#OnEditorActionListener true
}
false
})
The feature used in your example is a SAM constructor. The setOnEditorActionListener listener takes an OnEditorActionListener as its parameter. This interface only has a single method that you have to implement, which makes it a Single Abstract Method (SAM) interface.
The full syntax for using this method in Java would be:
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
attemptLogin();
return true;
}
});
The one-to-one conversion to Kotlin would give you:
mPasswordView.setOnEditorActionListener(object: TextView.OnEditorActionListener{
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
attemptLogin()
return true
}
})
Kotlin, however, allows you to use methods that take SAM interfaces as their parameter with a more concise syntax, by passing in a lambda instead. This is called a SAM conversion:
mPasswordView.setOnEditorActionListener { v, actionId, event ->
attemptLogin()
true
}
SAM conversions automatically determine which interface this lambda corresponds to, but you can specify it explicitly by using something called a SAM constructor, this is what's in your sample code. A SAM constructor returns an object that implements the given interface, and makes the lambda you've passed to it its single method's implementation.
mPasswordView.setOnEditorActionListener( TextView.OnEditorActionListener { v, actionId, event ->
attemptLogin()
true
})
This is redundant in this specific situation, because there is only one method called setOnEditorActionListener. But if there were multiple methods with this same name, that took different interfaces as parameters, you could use the SAM constructor to specify which overload of the method you want to call.
Official docs about SAM conversions