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);
}
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 UI of my app is totally built with code. It's an EMPTY ACTIVITY with the activity_main.xml deleted, and utilizes the following style:
<style name="myTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="android:windowBackground">#drawable/splashbg</item>
</style>
The basic activity looks like this, with mainView (global variable) acting as the root view:
class MyApp: AppCompatActivity(), View.OnTouchListener, OnMapReadyCallback {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainView = FrameLayout(this).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
setBackgroundColor(Color.WHITE)
}
setContentView(mainView)
supportActionBar?.hide()
}
override fun onBackPressed() {
println("onBackPressed")
super.onBackPressed()
}
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
println("dispatchKeyEvent")
return super.dispatchKeyEvent(event)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
println("onKeyDown")
return super.onKeyDown(keyCode, event)
}
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
println("onKeyUp")
return super.onKeyUp(keyCode, event)
}
}
It should also be noted that the app has been migrated to AndroidX and is thus utilizing these libraries:
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
The app works as expected except for the key-event methods. I'm trying to intercept the onBackPressed event, but nothing seems to be working. None of the events, not onKeyDown, onKeyUp, onBackPressed, or dispatchKeyEvent are responding. The console does not print any of the println outputs but instead, I get these whenever I press the phone's physical BACK key:
2019-11-14 03:57:44.541 16056-16056/? I/GoogleInputMethod: onKeyDown() : keyCode = 4, event = KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=158, metaState=0, flags=0x48, repeatCount=0, eventTime=280842030, downTime=280842030, deviceId=5, source=0x101 }
2019-11-14 03:57:44.590 1434-1540/? D/BaseMiuiPhoneWindowManager: keyCode:4 down:false eventTime:280842082 downTime:280842030 policyFlags:22000002 deviceId:5 isScreenOn:true keyguardActive:false repeatCount:0
Is there some configuration or setting that I might have missed?
TIA.
EDIT: I should also add that this issue occurs when the keyboard is visible. Pressing the BACK key dismisses the keyboard but does not trigger the key events.
Use this for back press key
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
for device back button try using
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
// your code
return true;
}
return super.onKeyDown(keyCode, event);
}
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 a function that shrinks the size of a view on ACTION_DOWN, and returns it to original size on ACTION_UP. This function is strictly for aesthetics. I have setOnClickListeners on the buttons so they can (in theory...) execute code when the button is pressed.
private fun scaleButton(theButton:View, grow:Boolean){
theButton.setOnTouchListener(View.OnTouchListener { v, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
v.isPressed = true
if (grow) {
v.animate().scaleX(1.04F).scaleY(1.04F).setDuration(50)
} else {
v.animate().scaleX(0.97F).scaleY(0.97F).setDuration(50)
}
} else if (event.action == MotionEvent.ACTION_UP) {
v.isPressed = false
v.animate().scaleX(1.0F).scaleY(1.0F).setDuration(100)
}
false
})
}
The problem is setOnClickListener is never called.
onCreate:
scaleButton(button1,false)
scaleButton(button2,true)
scaleButton(button3,false)
button1.setOnClickListener {
println("Button 1 Pressed")
}
button2.setOnClickListener {
println("Button 2 Pressed")
}
button3.setOnClickListener {
println("Button 3 Pressed")
}
How can I both scale the button via animation (on touch) AND trigger setOnClickListener
Your click listener will never fire because the touch listener is supposed to tell the system when the elemnt was clicked. Yours isn't. Either add a performClick at the appropriate time, or put everything into the touch listener and perform the click on action_up
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.