Search code examples
pythontimertoga

how to add a timer to a Toga app (python) running a task every interval, using Toga's event loop?


My toga app needs to run a task every X seconds. I prefer a multi-platform solution where possible.

I asked an AI and its solution used an external dependency ("schedule") + additional loop + threading:

def create_timer(self, interval, callback):
    def run_callback():
        schedule.every(interval).seconds.do(callback)
        while True:
            schedule.run_pending()
            threading.Event().wait(interval)  
    timer_thread = threading.Thread(target=run_callback)
    timer_thread.daemon = True  # Ensure thread terminates with app
    timer_thread.start()
    return timer_thread

I rather avoid external modules and adding loops (for performance, minimize resource usage, and simplicity).

How to implment a timer / interval based on Toga's own event loop?


Solution

  • I never used toga but I found information that it uses async loop and that it has functions

    So I took some example app from documentation and I created working example which displays current time using call_later().

    I use it in change() to execute the same function again after some time
    (similar to .after(milliseconds, callback) in tkinter)

    I don't know how to access app.loop (and label) in this function
    so I send app (and label) as parameters.
    But works also loop = asyncio.get_event_loop()

    import toga
    #import datetime
    import time
    #import asyncio
    
    def change(app, widget):
        # if code needs more time then better set next execution first
        loop = app.loop
        #loop = asyncio.get_event_loop()
        loop.call_later(1, change, app, widget)
        
        #widget.text = datetime.datetime.now().strftime('%Y.%m.%d %H.%M.%S')
        widget.text = time.strftime('%Y.%m.%d %H.%M.%S')
    
        #app.loop.call_later(1, change, app, widget)  # here it would need `1 - time_of_function_execution`
    
    def build(app):
        box = toga.Box()
    
        label = toga.Label("?")
        box.add(label)
    
        # execute first time without delay
        #app.loop.call_soon(change, app, label)
        #app.loop.call_later(0, change, app, label)
        change(app, label)
        
        return box
    
    def main():
        return toga.App("Timer", "org.beeware.toga.tutorial", startup=build)
    
    
    if __name__ == "__main__":
        main().main_loop()
    

    The same using class MyApp(toga.App) like in documentation for App

    because all is in class so I can use self. to access loop and label and I don't have to send it as parameters.

    import toga
    import time
    
    
    class MyApp(toga.App):
    
        def startup(self):
            self.main_window = toga.MainWindow()
            self.main_window.content = toga.Box()
            
            self.label = toga.Label("?")
            self.main_window.content.add(self.label)
    
            # execute first time without delay
            #self.loop.call_soon(self.change, self.label)
            #self.loop.call_later(0, self.change, self.label)
            self.change()
            
            self.main_window.show()
    
        def change(self):
            # if code needs more time then better set next execution first
            self.loop.call_later(1, self.change)
        
            self.label.text = time.strftime('%Y.%m.%d %H.%M.%S')
    
            
    if __name__ == '__main__':
        app = MyApp("Timer", "org.beeware.toga.tutorial")
        app.main_loop()