I have been working on an app with Kivy for some time now and am adding final touches. I have been searching for days now for a solution to my problem, to no avail. I have tried Threading, Clock, and running functions in various orders to try and get this to work. I have reproduced a working example below but made it as minimalistic as possible, so as to not make my post huge. In my real app's get_briefing
and strip_first_brief
functions, I have a couple of POST requests and a couple of GET requests that vary from 5-20 seconds to perform. I have therefore implemented time.sleep(5)
into each of these to emulate the working time. This gives the EXACT same result as my real app. It isn't the POST or GET functions alone that are stopping the animation, it's every process involved....
Because of these load times, I have also cut a corner to get to my loading screen wherein my button on_press
immediately changes screen, otherwise with card transition, the screen was getting stuck about 1/5 through the transition. I obviously have more than one problem to unpack in this program, and I think most would be solved if I could get around the "frozen app" issue...
If someone could please copy and test my provided program, and come back with a solution, I would be very grateful. I have been stuck for way too long on this (literally).
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, NoTransition, CardTransition, SwapTransition
from kivy.uix.button import ButtonBehavior
from kivy.uix.image import Image
from kivy.animation import Animation
import time
from threading import Thread
class ImageButton(ButtonBehavior, Image):
pass
class HomeScreen(Screen):
pass
class Results1Screen(Screen):
pass
class LoadScreen(Screen):
pass
GUI = Builder.load_file("main.kv")
class TestApp(App):
def build(self):
return GUI
def change_screen(self, screen_name):
screen_manager = self.root.ids['screen_manager']
screen_manager.current = screen_name
def widget_thread(self):
p1 = Thread(target=self.spin_widget())
p1.start()
#Clock.schedule_once(lambda dt: self.spin_widget(), 0)
def spin_widget(self):
widget = self.root.ids["load_screen"].ids["spinning_widget"]
anim = Animation(angle=-3000, duration=40)
anim.start(widget)
def immediate_screen1(self):
self.root.ids["screen_manager"].transition = NoTransition()
self.change_screen("load_screen")
self.root.ids["screen_manager"].transition = CardTransition()
def thread1(self, dest1, dest2):
self.thread = 1
p2 = Thread(target=self.get_briefing(dest1, dest2))
p2.start()
def get_briefing(self, dest1, dest2):
time.sleep(5)
def strip_first_brief(self, destX, etaX):
time.sleep(5)
#p3 = Thread(self.strip_first_brief(dest2, eta2))
#p3.start()
TestApp().run()
main.kv
#:import utils kivy.utils
#:include kv/homescreen.kv
#:include kv/results1screen.kv
#:include kv/loadscreen.kv
GridLayout:
cols: 1
FloatLayout:
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
rows: 1
pos_hint: {"top": 1, "left": 1}
size_hint: 1, .04
ScreenManager:
size_hint: 1, .8
pos_hint: {"top": .8, "left": 1}
id: screen_manager
HomeScreen:
name: "home_screen"
id: home_screen
Results1Screen:
name: "results_1_screen"
id: results_1_screen
LoadScreen:
name: "load_screen"
id: load_screen
homescreen.kv
<HomeScreen>:
FloatLayout:
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
TextInput:
id: manual_dest1
hint_text: "first destination"
text: self.text.upper() if self.text is not None else ''
size_hint: .7, .08
pos_hint: {"top": .75, "center_x": .5}
TextInput:
id: eta1
hint_text: "first ETA in UTC"
text: self.text.upper() if self.text is not None else ''
size_hint: .7, .08
pos_hint: {"top": .5, "center_x": .5}
Button:
text: "Strip TAF"
pos_hint: {"top": .15, "center_x": .5}
size_hint: .5, .08
padding: 20, 20
opacity: 1 if self.state == 'normal' else .5
on_press:
app.immediate_screen1()
app.widget_thread()
on_release:
app.thread1(manual_dest1.text, manual_dest2.text)
app.strip_first_brief(manual_dest1.text, eta1.text)
app.change_screen("results_1_screen")
results1screen.kv
<Results1Screen>:
FloatLayout:
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
Label:
rows: 1
text: "RESULTS\nblah\nblah\nblah"
pos_hint: {"top": .95, "center_x": .5}
size_hint: 1, .05
loadscreen.kv
<LoadScreen>:
FloatLayout:
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
Label:
text: "loading"
SpinningWidget:
id: spinning_widget
source: "icons/turbine.png"
angle: 0
size_hint: .15, .15
pos_hint: {"center_x": .5, "center_y": .6}
<SpinningWidget@Image>
angle: 0
canvas.before:
PushMatrix
Rotate:
angle: root.angle
axis: 0, 0, 1
origin: root.center
canvas.after:
PopMatrix
When you do trial this, there's no need to input a destination or eta obviously. Also there's no image supplied, but you'll see the square spin about 2 degrees before getting stuck, then see it again right at the last second trying to spin again. I apologise for the lengthy post, but I have no other way to solve this. Please help....
p2 = Thread(target=self.get_briefing(dest1, dest2))
This calls self.get_briefing
and passes the result to target, i.e. it's equivalent to:
result = self.get_briefing(dest1, dest2)
p2 = Thread(target=result)
You probably want to pass in the function to be called as the argument, something like
p2 = Thread(target=lambda: self.get_briefing(dest1, dest2))