Search code examples
pythonkivykivy-language

Kivy does not play gif in Popup while other code is running


In my Kivy app, I have a function that takes a long time to complete. I made a popup to inform the user the function is running. I want to have a gif animation so that the user knows the app did not crash. In testing, the gif played as expected in the popup, until I add the long running function, then only a stationary image is displayed. Everything else works as expected (e.g. the popup closes at the end of the function).

tl;dr

How can I make a gif continue to play in my kivy app while a function is being executed?

Code Summary

I largely followed the code provide by Dirty Penguin's answer in Building a simple progress bar or loading animation in Kivy?:

from kivy.app import App
from kivy.uix.popup import Popup
from kivy.properties import ObjectProperty
from kivy.clock import Clock

import time, threading

class RunningPopup(GridLayout):
    fpop = None

    def set_pop(self, pwin):
        self.fpop = pwin

    def close(self):
        self.fpop.dismiss()

class ExampleApp(App):
    def show_popup(self):
        self.pop_up = RunningPopup()
        self.pop_up.open()

    def process_button_click(self):
        # Open the pop up
        self.show_popup()

        # the original code suggested by dirty penguin
        # mythread = threading.Thread(target=self.really_long_function)
        # mythread.start()

        # I've had better luck with the Clock.schedule_once
        Clock.schedule_once(self.really_long_function)

    def really_long_function(self):
        thistime = time.time() 
        while thistime + 5 > time.time(): # 5 seconds
            time.sleep(1)

        # Once the long running task is done, close the pop up.
        self.pop_up.dismiss()

if __name__ == "__main__":
    ExampleApp().run()

My KV file:

<RunningPopup>:
    rows: 3

    Label:
        size_hint_y: 0.2
        text: 'Experiments are running ... '
        bold: True
        color: hex('#0DB14B')

    Label:
        size_hint_y: 0.2
        text: 'Please be patient, this may take time.'
        color: hex('#0DB14B')

    Image:
        size_hint_y: 0.6
        id: loading_animation_gif
        height: dp(200)
        source: './graphics/loading.gif'
        center_x: self.parent.center_x
        center_y: self.parent.center_y
        allow_stretch: True
        size_hint_y: None
        anim_delay: 0.05
        mipmap: True

Tried

  • Using threading - this does not run in parallel, the long function runs and then the popup flashes open and closed
  • Using kivy.clock schedule_once - this appears to work the best in that the popup opens and closes as expected / during the long running function
  • Using a Zip file of images instead of a gif file (as suggested here: Gif Animation not playing in Kivy App)

Related


Solution

  • Using the Thread will work. Here is a modified version of your code that uses threading. I had to make a few changes just to get your code to run:

    from kivy.app import App
    from kivy.lang import Builder
    from kivy.uix.popup import Popup
    
    import time, threading
    
    kv = '''
    #:import hex kivy.utils.get_color_from_hex
    Button:
        text: 'doit'
        on_release: app.process_button_click()
        
    <RunningPopup>:
        GridLayout:
            rows: 3
        
            Label:
                size_hint_y: 0.2
                text: 'Experiments are running ... '
                bold: True
                color: hex('#0DB14B')
        
            Label:
                size_hint_y: 0.2
                text: 'Please be patient, this may take time.'
                color: hex('#0DB14B')
        
            Image:
                size_hint_y: 0.6
                id: loading_animation_gif
                height: dp(200)
                source: 'elephant.gif'  # my gif file
                center_x: self.parent.center_x
                center_y: self.parent.center_y
                allow_stretch: True
                size_hint_y: None
                anim_delay: 0.05
                mipmap: True
    '''
    
    class RunningPopup(Popup):
        fpop = None
    
        def set_pop(self, pwin):
            self.fpop = pwin
    
        def close(self):
            self.fpop.dismiss()
    
    class ExampleApp(App):
        def build(self):
            return Builder.load_string(kv)
    
        def show_popup(self):
            self.pop_up = RunningPopup()
            self.pop_up.open()
    
        def process_button_click(self):
            # Open the pop up
            self.show_popup()
    
            # the original code suggested by dirty penguin
            mythread = threading.Thread(target=self.really_long_function)
            mythread.start()
    
            # I've had better luck with the Clock.schedule_once
            # Clock.schedule_once(self.really_long_function)
    
        def really_long_function(self, *args):
            thistime = time.time()
            while thistime + 5 > time.time(): # 5 seconds
                time.sleep(1)
    
            # Once the long running task is done, close the pop up.
            self.pop_up.dismiss()
    
    if __name__ == "__main__":
        ExampleApp().run()