Kivy and Threading issue

348 views
Skip to first unread message

PyStock None

unread,
Apr 10, 2021, 10:00:05 PM4/10/21
to Kivy users support
Hello everybody. Today I'm going to ask a question about Kibi and multithreading. The content of the app to be created is to request the desired bitcoin from the websocket server and receive real-time information about it and display the information on the table widget inside the app. When I click the button, the list of bitcoins is displayed as a drop-down menu, and when I select one of them and press another button, a request is sent to the web server. However, the app does not work properly due to my lack of knowledge about kibi and threading. The problem is that the drop-down menu for selecting a stock doesn't work after making a real-time quote request. I would like to ask where the problem is and suggest a solution. The following is the source code

<main.py>
fromfrom kivymd.app import MDApp
from kivy.metrics import dp
from kivy.clock import Clock, mainthread
from kivy.uix.dropdown import DropDown
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.datatables import MDDataTable
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.lang import Builder
from kivy.core.window import Window

import os
import jwt
import uuid
import hashlib
import json
import websocket
import time
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from ast import literal_eval
from threading import Thread
from queue import Queue

class UpbitReal:
    def __init__(self, root):
        self.root = root
        websocket.enableTrace(True)
        self.ws = websocket.WebSocketApp(
        url="wss://api.upbit.com/websocket/v1",
        on_message=lambda ws, msg: self.on_message(ws, msg),
        on_error=lambda ws, msg: self.on_error(ws, msg),
        on_close=lambda ws: self.on_close(ws),
        on_open=lambda ws: self.on_open(ws))
        self.running = False

    def on_message(self, ws, msg):
        msg = json.loads(msg.decode('utf-8'))
        self.root.queue.put(msg)

    def on_error(self, ws, msg):
        print("Error: ",msg)

    def on_close(self, ws):
        print("closed")
        self.running = False

    def on_open(self, ws):
        sendData = '[{"ticket":"test"},{"type":"ticker","codes": ["KRW-BTC"]}]'
        th = Thread(target=self.activate, args=(sendData, ), daemon=True)
        th.start()

    def activate(self, code):
        self.ws.send(code)


    def start(self, ws):
        self.running = True
        self.ws.run_forever()

class Main(MDApp):
    def __init__(self):
        super().__init__()
        querystring = {"isDetails":"false"}
        req = Request(url)
        req.add_header("isDetails", "false")
        response = urlopen(url).read().decode("utf-8")
        temp = response[1:-1]
        keys = literal_eval(temp)
        self.codes = {}
        self.queue = Queue()
        self.code_of_requested = []
        for i in keys:
        self.codes[i["market"]] = {"hname": i["korean_name"]}
    
    def build(self):
        self.ui = Builder.load_file(".//main.kv")
        self.dropdown = DropDown()
        for market in self.codes.keys():
            btn = Button(text='%s' % market, size_hint_y=None, height=100)
            btn.bind(on_release=lambda btn: self.dropdown.select(btn.text))
        self.dropdown.add_widget(btn)
        self.tables = MDDataTable(
                size_hint=(0.95, 0.95),
                use_pagination=False,
                check=False,
                column_data=[
                ("No.", dp(30)),
                ("Status", dp(30)),
                ("Signal Name", dp(60))
                ],
                row_data=[
                ],
                sorted_on="Schedule",
                sorted_order="ASC",
                elevation=2
                )
        self.ui.ids.info.add_widget(self.tables)
        self.dropdown.bind(on_select=lambda instance, x: setattr(self.ui.ids.codes, 'text', x))
        self.ui.ids.codes.bind(on_release=self.dropdown.open)
        self.ui.ids.request.bind(on_release=self.add_realinfo)
        self.worker = UpbitReal(self)
        return self.ui

    def requestCode(self, codes):
        sendData = '[{"ticket":"test"},{"type":"ticker","codes": [%s]}]'%codes
        self.ws.send(sendData)

    def show_info(self):
        while True:
            data = self.queue.get()
            if data != None:
                code = data.get("code")
                current_price = data.get('trade_price')
                temp_time = data.get('trade_time')
                temp_hour = int(temp_time[:2]) + 9 if int(temp_time[:2]) + 9 < 24 else int(temp_time[:2]) - 15
                hour = str(temp_hour) if temp_hour > 9 else "0"+str(temp_hour)
                min = temp_time[2:4]
                sec = temp_time[4:]
                time_info = hour+":"+min+":"+sec
            print(data)
            time.sleep(0.1)

    def add_realinfo(self, instance):
                Clock.schedule_once(self.worker.start, timeout=3)
                Thread(target=self.show_info, args=()).start()

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

[main.kv]
Screen:
    BoxLayout:
        orientation: "vertical"
        BoxLayout:
            size_hint: 1.0, None
            height: 300
            Button:
                id: codes
                text: "Codes"
                size_hint: 0.7, 1.0
            Button:
                id: request
                text: "Request"
                size_hint: 0.3, 1.0
        BoxLayout:
            id: info

Robert

unread,
Apr 11, 2021, 3:02:34 AM4/11/21
to Kivy users support
Based on the symptoms you describe, the Kivy event loop is probably stalled.
Presumably because the app is making a network request from the UI thread thus stalling the event loop because it is also on the UI thread.

Maybe somebody else wants to dig through your code.

PyStock None

unread,
Apr 11, 2021, 5:31:05 AM4/11/21
to Kivy users support
Thank you a lot Robert.I wrote the code based on your advice. On pc it works fine.
I had a problem making it an Android app. 
I included websocket==0.2.1,websocket-client==0.58.0 in the requirements of buildozer.spec. 
Then, run "buildozer android debug deploy run logcat -> result.txt" and create a websocket client to terminate the app. 
When looking at the result.txt file I can see that there is a problem in the import websocket part of the source code. 
The websocket package says that there is no class called WebSocketApp. 
Do you know this problem?
1.png



Robert

unread,
Apr 11, 2021, 1:55:05 PM4/11/21
to Kivy users support
I am not familiar with either websocket or websocket-client.

It looks like websocket was either not or incompletely installed in the apk.

Typically a message like this will occur if there is a build issue.For example if the package is not pure Python (and does not have a recipe), or there is an issue with one or more of the package's dependencies.
A desktop build does not necessarily build on Android. Some background reading https://github.com/RobertFlatt/Android-for-Python/tree/main/Android-for-Python-Users

To look for a build issue:
'buildozer appclean', 'buildozer android debug' then look in the build log; probably for a warning or maybe an error.
Buildozer does not stop because in a Python context we can't know till run time if the issue is significant.
Reply all
Reply to author
Forward
0 new messages