Search code examples
pythonmultithreadinguser-interfacekivykivy-language

How to make Kivy app run inside a thread?


I have a very simple Kivy app that runs perfectly like this:

def run_form():
    rules_form().run()


if __name__ == '__main__':
    run_form()

But I want the program to continue while the rules_form window is open. I tried the following:

def run_form():
    rules_form().run()


if __name__ == '__main__':
    t = Thread(target=run_form)
    t.start()
    print("Hi")

When I do this, the program DOES print "hi", but the Kivy window that opens is white and is not responding.

How can I make it work?


Solution

  • I have recently been working with threads with Kivy and have done as Inclement has suggested. That is have Kivy run in the main thread then dispatch other tasks to another thread.

    Here is an example of an app I created that uses threads:

    from kivy.app import App
    from kivy.lang import Builder
    from kivy.uix.textinput import TextInput
    from kivy.properties import ListProperty
    from kivy.uix.boxlayout import BoxLayout
    from kivy.uix.behaviors import ButtonBehavior
    from kivy.uix.label import Label
    import webbrowser
    from wiki_recommendations import WikiSearcher
    
    
    class SearchBar(TextInput):
        articles = ListProperty()
    
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
            self.bind(text=self.on_text)
            self.bind(articles=self.on_articles)
    
        def on_text(self, *args):
            WikiSearcher().get_search_results(self.text, self)
    
        def on_articles(self, *args):
            self.parent.ids['recommendations'].update_recommendations(self.articles)
    
    
    class SearchItem(ButtonBehavior, Label):
    
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
            self.url = ''
    
        def on_release(self):
            webbrowser.open(self.url)
    
    
    class Recommendations(BoxLayout):
    
        def update_recommendations(self, recommendations: list):
            for (search_item, recommendation) in zip(self.children, recommendations):
                search_item.text = recommendation
                search_item.url = 'https://en.wikipedia.org/wiki/' + recommendation
    
    
    kv = """
    <SearchItem>:
        canvas.before:
            Color: 
                rgba: [0.8, 0.8, 0.8, 1] if self.state == 'normal' else [30/255, 139/255, 195/255, 1]
            Rectangle:
                size: self.size
                pos: self.pos
            Color:
                rgba: 0, 0, 0, 1
            Line:
                rectangle: self.x, self.y, self.width, self.height
                
        color: 0, 0, 0, 1
        
    BoxLayout:
        canvas.before:
            Color: 
                rgba: 1, 1, 1, 1
            Rectangle:
                size: self.size
                pos: self.pos
                
        orientation: 'vertical'
        padding: self.width * 0.1
        spacing: self.height * 0.1
    
        SearchBar:
            size_hint: 1, 0.2
            multiline: False
            font_size: self.height*0.8
    
        Recommendations:
            id: recommendations
            orientation: 'vertical'
            SearchItem
            SearchItem
            SearchItem
            SearchItem
            SearchItem
    """
    
    
    class SearchApp(App):
    
        def build(self):
            return Builder.load_string(kv)
    
    
    if __name__ == '__main__':
        SearchApp().run()
    

    This app has users type in a search bar and automatically recommends Wikipedia pages. A screen shot can be seen below:

    enter image description here

    This code uses threads to get the recommendations (so as not to interrupt the users typing). A different file called wiki_recommendataions.py creates these threads then dispatches an API call.

    import wikipedia
    import threading
    
    
    def thread(function):
        def wrap(*args, **kwargs):
            t = threading.Thread(target=function, args=args, kwargs=kwargs, daemon=True)
            t.start()
    
            return t
        return wrap
    
    
    class WikiSearcher:
    
        @thread
        def get_search_results(self, text: str, root):
            """
            Gets the top Wikipedia articles
            :param text: The text to be searched.
            :param root: The object that calls this function - useful for returning a result.
            :return:
            """
            root.articles = wikipedia.search(text)
    

    As the user types in the search bar the WikiSearcher object is instantiated and the get_search_results() is called. This updates the articles property of the SearchBar which in turn updates the children of Recommendations (a BoxLayout). These children are essentially just Buttons which direct the user to the recommended page when they are pressed.