community
community copied to clipboard
keyboard issue when switching between TextInput widgets on android
Software Versions
- Python:3.7
- OS: android 11
- Kivy: 2.0.0
- Kivy installation method: using python for android
Describe the bug
when we have more then on TextInput in same screen on android the keyboard some times not appears when switching directly from TextInput to another only work fine if I close the keyboard and focus the other one
Code and Logs and screenshots
from kivy.core.window import Window
from kivy.lang import Builder
from kivymd.app import MDApp
kv = '''
BoxLayout:
orientation:'vertical'
spacing:'20dp'
padding:'20dp'
TextInput
TextInput
TextInput
TextInput
'''
class TestApp(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Window.keyboard_anim_args = {"d": 0.2, "t": "linear"}
Window.softinput_mode = "below_target"
def build(self):
return Builder.load_string(kv)
TestApp().run()
Additional context
the video will show the issue on android
https://user-images.githubusercontent.com/52833102/142660765-840414b5-e552-4deb-936a-ea6d5ab8d606.mp4
Hello, I have just fixed this bug here using the keyboard_mode 'managed'. The problem happens because on_focus from each textfield is calling show_keyboard() or hide_keyboard(), but they behave like out-of-sync dancers.
When you click on TextInput 1, android keyboard appears (it executes show_keyboard())
When you click outside TextInput 1, android keyboard disappears (it executes hide_keyboard())
When you click on textfield_1 and then you click on textfield_2, you have two possibilities:
POSSIBILITY ONE:
textfield_1.focus becomes False
textfield_2.focus becomes True
OR
POSSIBILITY TWO:
textfield_2.focus becomes True
textfield_1.focus becomes False
Isn't the two possibilities the same?
No! The order is different and this makes total difference. I can't determine why for some people textfield_1 becomes unfocused before focusing on textfield_2, and for some people textfield_1 becomes unfocused after focusing on textfield_2 (causing the bug, because textfield_1 will call hide_keyboard() when unfocused, and your android keyboard will close).
The solution I found was modifying the on_touch_down function with a simple decorator that checks the touch position (touch.pos) and update self.focus accordingly. Also, I delay the show_keyboard() function in 100ms, so it will be certain that the other textfield has already been unfocused and dispatched its events.
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.factory import Factory as F
from kivy.uix.textinput import TextInput
from kivy.core.window import Window
Window.softinput_mode = "below_target"
class TextInputFixed(TextInput):
def on_focus(self, instance, value, *args):
if value:
Clock.schedule_once(self.create_keyboard, .1)
else:
self.hide_keyboard()
def create_keyboard(self, *args):
self.show_keyboard()
def remove_focus_decorator(function):
def wrapper(self, touch):
if not self.collide_point(*touch.pos):
self.focus = False
function(self, touch)
return wrapper
@remove_focus_decorator
def on_touch_down(self, touch):
super().on_touch_down(touch)
class ScreenOne(F.Screen):
pass
Builder.load_string("""
<ScreenOne>:
BoxLayout:
orientation: 'vertical'
Button:
Label:
text: 'Username:'
size_hint_y: None
height: self.texture_size[1]
TextInputFixed:
pos_hint: {'center_x': .5}
foreground_color: 1,0,0,1
keyboard_mode: 'managed'
on_text_validate: tf.focus = True
multiline: False
Label:
text: 'Password:'
size_hint_y: None
height: self.texture_size[1]
TextInputFixed:
id: tf
pos_hint: {'center_x': .5}
keyboard_mode: 'managed'
Widget:
""")
class MainApp(App):
def build(self):
self.screen_one = ScreenOne()
return ScreenOne()
MainApp().run()
https://user-images.githubusercontent.com/23220309/167855805-7c84274f-6867-4d07-8011-70d8e13658e6.mp4
https://github.com/kivy/kivy/blob/master/kivy/uix/behaviors/focus.py#L400
Can you confirm the issue is here? Would explain why (prior to example above) it wasn't replicated. Or were you seeing the same prior to setting keyboard_mode to managed?
https://github.com/kivy/kivy/blob/master/kivy/uix/behaviors/focus.py#L400
Can you confirm the issue is here? Would explain why (prior to example above) it wasn't replicated. Or were you seeing the same prior to setting keyboard_mode to managed?
Yes, it is happening due to _on_focus being called by two TextInputs in an unorganized and non-deterministic manner. I was printing who was executing these functions first, and it was a complete mess!
Sometimes T2 would say "I will create keyboard now", and afterwards T1 would say "I will destroy keyboard now", but the order should always be 1) The keyboard disappears; 2) The keyboard appears.
But Eventloop.window.request_keyboard has a callback that executes self.focus = False (imagine what 'auto' will do when this happens), so whenever the android keyboard was requested by one textfield (the smartphone blinks, closing the previous keyboard and adding a new one instantly), the other textfield changes focus afterwards, due to Event.loop callback and it kills the android keyboard.
I have never used keyboard_mode 'manage' before and the bug was always happening on my current phone, but other users haven't reported the same issue until this week. I didn't even knew it 'manage' existed. During this night I was able to reproduce the issue both on computer and smartphone deterministically, and the code I posted prevented the bug on both.
Sorry if my explanation is not the best, but using the debugger makes it clear what is really happening there, probably there is a better fix but I will use this workaround ('manage') for now
Not seeing where your debug prints were sort of limits the usefulness; it probably is somewhat a race condition - needing a wait for the subsequent request yea would've been how I would've interpreted what you were seeing too. Did you try with 0 there instead of .1, as in schedule it for the next clock tick. In which case probably no harm PR'ing that...
On the other hand/further thinking; if that retains the issue then presumably due to it being a property change; on two widgets both being propagated on next tick (which I believe is the case now, rather than an immediate firing) with the order being unspecified.
I've tested with Clock.schedule_once(self.create_keyboard), but then the android keyboard disappears when you click on two textfields consecutively. Clock.schedule_once(self.create_keyboard, .05) still had the same problem, and Clock.schedule_once(self.create_keyboard, .075) works fine, Clock.schedule_once(self.create_keyboard, .1) seems safer and nice