14

in Kivy, when I press the back button on my android device it throws me out of the application. is there a way to return back to the previous screen using the Kivy language and not python? this is what I have written in kivy:

<MyAppClass>:
AnchorLayout:
    anchor_x : 'center'
    anchor_y : 'top'
    ScreenManager:
        size_hint : 1, .9
        id: _screen_manager

        Screen:
            name:'screen1'
            StackLayout:
                # irrelevant code

        Screen:
            name:'screen2'
            StackLayout:
                # irrelevant code

I need to manipulate the screen manager and its screens from python... if I can do so I will be ok with python.

BenMorel
  • 34,448
  • 50
  • 182
  • 322
toufikovich
  • 794
  • 1
  • 6
  • 19

4 Answers4

23

Kivy on android binds the back button to the esc button so binding and listening to esc button in your app would help you handle how your app behaves when the back button is pressed.

In other words in your app when testing it on your desktop listen to the escape key from the system keyboard, this will be automatically be translated to being the back button on your android device. Something like::

def on_start():
    from kivy.base import EventLoop
    EventLoop.window.bind(on_keyboard=self.hook_keyboard)

def hook_keyboard(self, window, key, *largs):
    if key == 27:
       # do what you want, return True for stopping the propagation
       return True 
qua-non
  • 4,152
  • 1
  • 21
  • 25
12

i guess that i have solved it but should thank both @inclement and @qua-non! your answers guys led me to the right way! so in kv i assume that i gave an id to my screen manager (please refer to my question where i have written the kv code) , in python i should do the following:

    from kivy.core.window import Window
    from kivy.properties import ObjectProperty

    class MyAppClass(FloatLayout):#its a FloatLayout in my case
        _screen_manager=ObjectProperty(None)
        def __init__(self,**kwargs):
            super(MyAppClass,self).__init__(**kwargs)
            #code goes here and add:
            Window.bind(on_keyboard=self.Android_back_click)

        def Android_back_click(self,window,key,*largs):
            if key == 27:
                self._scree_manager.current='screen1'#you can create a method here to cache in a list the number of screens and then pop the last visited screen.
                return True

    class MyApp(App):
        def build(self):
            return MyAppClass()

    if __name__=='__main__':
        MyApp().run()
toufikovich
  • 794
  • 1
  • 6
  • 19
  • 2
    No. You must declare the ObjectProperty at class level, not within the __init__ function. – inclement Nov 20 '13 at 13:59
  • i suppose you are right but worked in the init and in the class level. but the right way is where you said it should be.thank you again... – toufikovich Nov 20 '13 at 15:19
  • It didn't work in the init, it's just you didn't notice because of a coincidence where some other stuff you did replicated the right behaviour. – inclement Nov 20 '13 at 16:13
  • I can't say I understood what you mean.the object properties should be at the class level and they are either in init or above it. It should work both ways – toufikovich Nov 20 '13 at 16:41
  • 1
    By 'at class level' I mean where you currently have it in your example. This is the one and only way to have properties work right, they won't get properly bound as properties if you do it in __init__ . I think kv language also implicitly creates properties, which is why your example works either way - the kv language _screen_manager declaration overrides what the __init__ function did. – inclement Nov 20 '13 at 16:48
  • Exactly my friend! I should admit I have developed a big application and it is impossible to post all of it here so yeah lots of stuff is handled by the kV language and this is why I wanted to manipulate these objects in kV with python. – toufikovich Nov 20 '13 at 18:46
6

This is certainly possible. Here's a short example app with the method I use to do this:

from kivy.utils import platform
from kivy.core.window import Window

class ExampleApp(App):
    manager = ObjectProperty()

    def build(self):
        sm = MyScreenManager()
        self.manager = sm
        self.bind(on_start=self.post_build_init)
        return sm

    def post_build_init(self, *args):
        if platform() == 'android':
            import android
            android.map_key(android.KEYCODE_BACK, 1001)

        win = Window
        win.bind(on_keyboard=self.my_key_handler)

    def my_key_handler(self, window, keycode1, keycode2, text, modifiers):
        if keycode1 in [27, 1001]:
            self.manager.go_back()
            return True
        return False

This should give the right basic idea, but a few notes:

  • ScreenManager doesn't keep track of the previous screens, it's up to you to implement this how you like. My example assumes you defined a class MyScreenManager with a go_back method.
  • It might not be necessary to bind to on_start and run post_build_init, this is just how the example I originally used did it (see below). It might be important sometimes though, possibly if the window is not initialised when build() is run, and the original mailing list post suggests the author needed it for some reason.
  • The example listens for keycodes 27 or 1001. As qua-non said while I was writing this, the former listens for esc, so you can get the same behaviour on desktop.
  • I didn't try without the android.map_key line, but it seems like it may not be necessary.
  • You mention you want to use kivy language and not python. You need to do some python to get this result, and I don't see a way around that (it's not really the domain of the kv language). I guess you could shift some stuff to kv by defining a 'go_back' event somewhere and triggering this when the key is pressed, along with binding your screenmanager to watch that event, but it seems like a long way around.

I based my code on the mailing list thread at https://groups.google.com/forum/#!topic/kivy-users/7rOZGMMIFXI . There might be a better way, but this is quite functional.

inclement
  • 29,124
  • 4
  • 48
  • 60
  • i have edited my request... maybe it will be much clearer, sorry it was an abstract question first time. – toufikovich Nov 20 '13 at 11:36
  • So what is your real problem? You don't know how to change the current screen? You don't know how to get a reference to the screenmanager in python? – inclement Nov 20 '13 at 11:49
  • my problem is how to get reference to the screenmanager in python: could it be like _screen_manager=ObjectProperty(None)? then self._screen_manager.current = 'screen1' ? – toufikovich Nov 20 '13 at 11:53
  • 1
    Yep, that's a great way to do it. Don't forget to also set set the objectproperty to actually point at the screenmanager in your kv file though, which you can do with ids. – inclement Nov 20 '13 at 12:04
  • i have tried the following : _screen_manager=ObjectProperty(ScreenManager()), then in the function where to change the screens on key 27 press event i added : self._screen_manager.current='screen1' when testing it i had 'no screen with name "screen1"' , i am sorry to bother you @inclement this much but i am so close... – toufikovich Nov 20 '13 at 12:16
  • The problem is that your objectproperty contains a new screenmanager that you instantiated right there. Instead you should do `_screen_manager = ObjectProperty()` then in kv language `_screen_manager: _screen_manager` to reference it via the id you gave it. – inclement Nov 20 '13 at 12:29
  • 2
    Remove: 'if platform() == 'android':' 'import android' and 'android.map_key(android.KEYCODE_BACK, 1001)'. This is not necessary anymore and lead to errors with closing keyboard when TextInput is active. – NumesSanguis Aug 31 '15 at 22:41
  • @inclement is there any way to listen for volume button events using Python, Kivy, Pyjnius. Thanks in advance – Lakshmi Vallabh Oct 09 '22 at 14:20
  • @LakshmiVallabh probably yes but I don't know what the state of it is. The ideal way for it to work would be for SDL2 to detect it and pass it to the kivy app as a button press, but I don't know if SDL2 is set up for that. – inclement Oct 09 '22 at 19:07
2

Now all the way in 2020 I'm using:

Clock.schedule_once(lambda x: Window.bind(on_keyboard=self.hook_keyboard))

in combination with a similar hook_keyboard method to the other answers, to delay the bind in my build method. Works fine, but none of these other ways ways seemed to work for me anymore.

fcdt
  • 2,371
  • 5
  • 14
  • 26
nihilok
  • 1,325
  • 8
  • 12