'kivy screen freeze when sending a requests over internet
I am totally new in kivy and I was working this as a practice project. I searched a lot but can't find the solution. Basically the problem is when I send a requests over internet my kivy screen freeze for some time. Because of this my spinner(MDSpinner) is not working. Please kindly anyone help me.
My main.py code:
from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.datatables import MDDataTable
from kivy.uix.screenmanager import ScreenManager, Screen
import requests
import shutil
from bs4 import BeautifulSoup as Soup
import re
import os
#Not Real Url for some security reason
Login_URL = "http://some_url.com/login"
Back_URL = "http://some_url.com/info"
Profile_URL = "http://some_url.com/profile"
SESSION = None
sm = ScreenManager()
stu_name = None
stu_id = None
def login(uname_passw):
uname,passw = uname_passw.split(":::")
s = requests.Session()
data = {'email': uname, 'password': passw}
try:
r = s.post(Login_URL, data=data)
text = r.text
except:
text = "<div>Net Problem > Login > </div>"
soup = Soup(text, 'html.parser')
error = soup.find(class_="error_msg")
if error is not None:
error_msg = error.get_text()
error_msg = error_msg.split(".")[0] + "."
return error_msg
else:
global SESSION
SESSION = s
return True
def get_info(session):
s = SESSION
url = Back_URL
try:
r = s.get(url)
text = r.text
except:
text = "<div>Net Problem > Get_Info > </div>"
soup = Soup(text, "html.parser")
tables = soup.find_all(class_="table table-striped table-responsive table-bordered table-hover")
stu_basic_info = tables[0]
stu_drop_change = tables[1]
basic_tds = stu_basic_info.find_all("td")
basic_finfo = []
for td in basic_tds:
t = td.get_text()
if "Admission Fee" in str(t):
t = t + soup.find(id="sum_admission_fee").get("value")
basic_finfo.append(t)
drop_tds = stu_drop_change.find_all("td")
drop_change_finfo = []
for td in drop_tds:
t = td.get_text()
drop_change_finfo.append(t)
basic_info = {}
for i in basic_finfo:
if "Attempted credit" in i:
break
txt = i.split(":")
points = txt[0]
answer = txt[1]
pattern = '[A-z0-9.]+'
s1 = re.findall(pattern, points)
s1 = " ".join(s1)
s2 = re.findall(pattern, answer)
s2 = " ".join(s2)
basic_info[s1] = s2
total_fee = basic_info['Total Fee']
discount = basic_info['Waiver Discount']
payable = str(int(total_fee) - int(discount))
basic_info['Payable Amount'] = payable
d = []
for i in drop_change_finfo:
c = " ".join(re.findall(pattern, i))
d.append(c)
drop_change_finfo = d
numbers = []
names = []
for i in drop_change_finfo:
r = " ".join(re.findall('[0-9]+', i))
if r != '':
numbers.append(r)
r2 = " ".join(re.findall('[A-z]+', i))
if r2 != '':
names.append(r2)
extra_info = {}
for name, num in zip(names, numbers):
extra_info[name] = num
return basic_info, extra_info
class LoginScreen(Screen):
def on_pre_enter(self, *args):
dir_list = os.listdir(os.getcwd())
for file in dir_list:
if "img" in file:
os.remove(file)
def user_login(self):
self.ids.spinner1.active = True
uname = self.ids.uname.text
passw = self.ids.passw.text
uname_passw = f"{uname}:::{passw}"
self.user_login_var = login(uname_passw)
if self.user_login_var is not True:
msg = self.ids.msg
msg.text = str(self.user_login_var)
msg.theme_text_color = "Error"
msg.font_style = "Subtitle1"
msg.pos_hint = {'center_x': 0.5, 'center_y': 0.6}
else:
sm.switch_to(InfoScreen(name='info'))
class InfoScreen(Screen):
def on_pre_enter(self, *args):
self.info_dict, self.extra_info = get_info(SESSION)
def on_enter(self, *args):
global stu_name
global stu_id
stu_name, stu_id = self.info_dict['Student Name'], self.info_dict['Student ID']
nickname = stu_name.split(" ")
nickname = nickname[-1]
self.ids.tool.title = f"Hi, {nickname}"
a = []
for item in self.info_dict.items():
a.append(item)
for item in self.extra_info.items():
r, t = item
if "SL NO" in r:
pass
else:
a.append(item)
table = MDDataTable(
pos_hint={'center_x': 0.5, 'center_y': 0.5},
size_hint=(1, 0.7),
rows_num=21,
column_data=[
("Name", dp(30)),
("Details", dp(30))
],
row_data=a
)
self.ids.spinner.active = False
self.add_widget(table)
def change_screen(self, name):
if name == 'profile':
sm.switch_to(ProfileScreen(name='profile'))
else:
sm.switch_to(LoginScreen(name='login'))
class ProfileScreen(Screen):
def on_pre_enter(self, *args):
self.ids.welcome.text = f"Welcome {stu_name}"
s = SESSION
url = Profile_URL + stu_id
try:
r = s.get(url)
self.text = r.text
except:
self.text = "<div>Net Problem > Profile > </div>"
def on_enter(self, *args):
soup = Soup(self.text, 'html.parser')
tables = soup.find_all("table")
stu_data = tables[1]
img_data = tables[2]
academic_data = tables[3]
img_url = img_data.find("img").get("src")
response = requests.get(img_url, stream=True)
img_file = f"img_{str(stu_id)}.png"
with open(img_file, 'wb') as out_file:
shutil.copyfileobj(response.raw, out_file)
self.ids.bg_image.source = img_file
def change_screen(self):
sm.switch_to(InfoScreen(name='info'))
class SuLoginApp(MDApp):
def build(self):
global sm
sm.add_widget(LoginScreen(name='login'))
sm.add_widget(InfoScreen(name='info'))
sm.add_widget(ProfileScreen(name='profile'))
return sm
SuLoginApp().run()
sulogin.kv code:
<LoginScreen>:
MDSpinner:
id:spinner1
size_hint: None, None
size: dp(30), dp(30)
pos_hint: {'center_x': .5, 'center_y': .5}
active: False
MDLabel:
id:msg
text: "Student Login"
halign: "center"
pos_hint:{'center_x':0.5,'center_y':0.8}
font_style:'H2'
MDTextField:
id:uname
text: "CSE2201025001"
hint_text: "Username"
helper_text_mode: "on_focus"
pos_hint:{'center_x':0.5,'center_y':0.5}
size_hint:None,0.1
width:root.width*0.8
mode: "rectangle"
helper_text_mode: "on_error"
required: True
MDTextField:
id:passw
text:"123456"
hint_text: "Password"
helper_text_mode: "on_focus"
pos_hint:{'center_x':0.5,'center_y':0.38}
size_hint:None,0.1
width:root.width*0.8
mode: "rectangle"
helper_text_mode: "on_error"
required: True
MDRectangleFlatButton:
text: "Login"
pos_hint:{'center_x':0.5,'center_y':0.2}
size_hint:None,0.1
width:root.width*0.5
on_press: root.user_login()
<InfoScreen>:
BoxLayout:
orientation: 'vertical'
MDToolbar:
id:tool
elevation: 10
right_action_items: [['account-circle', lambda x:root.change_screen('profile') ]]
left_action_items: [['logout', lambda x:root.change_screen('login') ]]
Widget:
MDSpinner:
id:spinner
size_hint: None, None
size: dp(30), dp(30)
pos_hint: {'center_x': .5, 'center_y': .5}
active: True
<ProfileScreen>:
MDCard:
elevation: 10
radius: [36, ]
FitImage:
id: bg_image
size_hint_y: .35
pos_hint: {"top": 1}
radius: 36, 36, 0, 0
MDLabel:
id:welcome
halign:'center'
font_style:'H6'
MDRectangleFlatButton:
text:"Back"
on_press:root.change_screen()
pos_hint: {'center_x': .5, 'center_y': .2}
Solution 1:[1]
In order to keep your UI active while fetching/sending data you may use thread.
First create a new method like init_login in .py for creating a new thread,
def init_login(self):
"""Function to start a new thread each time."""
self.new_thread = threading.Thread(target = self.user_login) # Now call that function from this a new thread.
self.new_thread.start() # You can use this thread instance later if you want.
...
def user_login(self, *args):
self.ids.spinner1.active = True
uname = self.ids.uname.text
passw = self.ids.passw.text
...
Then call that method init_login instead of calling directly user_login in .kv as,
...
MDRectangleFlatButton:
text: "Login"
pos_hint:{'center_x':0.5,'center_y':0.2}
size_hint:None,0.1
width:root.width*0.5
on_press: root.init_login()
...
I did it this way just to keep the method user_login as it is (almost), you may try the other ways.
Solution 2:[2]
You shouldn't do the UI-related task in the main thread because the openGL operations should be done in the main thread. Otherwise, use @mainthread decorator.
Check this document - https://github.com/kivy/kivy/wiki/Working-with-Python-threads-inside-a-Kivy-application
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | ApuCoder |
| Solution 2 | rpi_guru |
