2

I am trying to build a simple messaging app, that uses rabbitmq as its message broker and kivy as its UI. So to receive incoming messages I have a receive function that is a loop, But when I try to multi-process the app and run it, kivy seems to be opening multiple windows. Kindly advise how I can tackle this issue.

.py file

import pika
from kivymd.app import MDApp
from kivy.lang import Builder
from kivymd.uix.card import MDCard
from database import Database as D
from kivy.core.window import Window
from multiprocessing import Process
from kivymd.uix.label import MDLabel
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDRectangleFlatButton
from kivy.uix.screenmanager import ScreenManager,Screen

Window.size = (400,700)

global logged_in_user
logged_in_user = ""

def receive():
    CREDENTIALS = pika.PlainCredentials('redbarker', 'Redbarker@20-21')
    PARAMETERS = pika.ConnectionParameters(credentials=CREDENTIALS)
    connection = pika.BlockingConnection(PARAMETERS)
    channel = connection.channel()
    channel.exchange_declare(exchange='system_exchange', exchange_type='topic', durable=True)
    result = channel.queue_declare(queue='', exclusive=True)
    queue_name = result.method.queue
    channel.queue_bind(exchange='system_exchange', queue=queue_name, routing_key="command.#")

    def callback(ch, method, properties, body):
        print(body)
        
    channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
    channel.start_consuming()

class LoginScreen(Screen):

    def validate_user(self):
        uname = self.ids.uname_input.text
        pword = self.ids.pword_input.text

        is_found = D().login(uname,pword)
        self.ids.uname_input.text = ""
        self.ids.pword_input.text = ""
        if is_found:
            logged_in_user = uname
            self.parent.current = "users_screen"

class SignupScreen(Screen):

    def signin(self):
        uname = self.ids.uname_input.text
        pword1 = self.ids.pword_input1.text
        pword2 = self.ids.pword_input2.text
        D().signup(uname,pword1,pword2)
        self.parent.current = "login_screen"

class UsersScreen(Screen):

    def to_inbox(self,text):
        InboxScreen().change_header(text)
        self.parent.current = 'inbox_screen'

    def add_user(self,name):
        container = self.ids.user_field
        button = MDRectangleFlatButton(
            text = name,
            font_style = "H6",
            text_color = (.95,.44,.49,1),
            line_color = (.95,.44,.49,1),
            pos_hint = {"center_x": .5, "center_y": .5},
            on_press = self.to_inbox(self.text),
        )
        container.add_widget(button)
        
class CreateGroupScreen(Screen):

    def add_group(self):
        container = self.ids.user_field
        name = self.ids.username.text
        if len(name) > 0:
            card = MDCard(
                size_hint_y = None,
                height = 50,
                line_color = (.95,.44,.49,1),
                radius = 10,
                padding = 10
            )
            label = MDLabel(
                halign = "left",
                text = name,
                theme_text_color = "Custom",
                text_color = (.95,.44,.49,1)
            )
            card.add_widget(label)
            container.add_widget(card)
            self.ids.username.text = ""

    def remove_all_users(self):
        for child in [child for child in self.ids.user_field.children]:
            self.ids.user_field.remove_widget(child)

class AddUserScreen(Screen):

    users = []

    def add_user(self):
        for name in self.users:
            pass

    def append_user(self):
        name = self.ids.username.text
        if len(name) > 0:

            container = self.ids.user_field
            card = MDCard(
                size_hint_y = None,
                height = 50,
                line_color = (.95,.44,.49,1),
                radius = 10,
                padding = 10
            )
            label = MDLabel(
                halign = "left",
                text = name,
                theme_text_color = "Custom",
                text_color = (.95,.44,.49,1)
            )
            card.add_widget(label)
            container.add_widget(card)
            self.users.append(name)
            self.ids.username.text = ""

    def remove_all_users(self):
        for child in [child for child in self.ids.user_field.children]:
            self.ids.user_field.remove_widget(child)

class InboxScreen(Screen):

    def change_header(self, text):
        self.ids.header.text = text

    def build_widget(self,message):
        container = self.ids.text_field
        layout = MDBoxLayout(
            size_hint_y = None,
            height = 50,
            padding = (10,0),
        )

        card = MDCard(
            radius = (0,10,0,10),
            padding = 10
        )

        place_holder = MDBoxLayout()

        label = MDLabel(
            halign = "right",
            text = message,
            font_style = "H6",
            theme_text_color = "Custom",
            text_color = (.95,.44,.49,1)
        )
        card.add_widget(label)
        layout.add_widget(place_holder)
        layout.add_widget(card)
        container.add_widget(layout)
    
    def send(self):
        message = self.ids.message_input.text
        if len(message) > 0:
            CREDENTIALS = pika.PlainCredentials('redbarker', 'Redbarker@20-21')
            PARAMETERS = pika.ConnectionParameters(credentials=CREDENTIALS)

            consumer = self.ids.header.text
            
            connection = pika.BlockingConnection(PARAMETERS)
            channel = connection.channel()
            channel.exchange_declare(exchange = 'system_exchange', exchange_type='topic', durable= True)
            channel.basic_publish(exchange='system_exchange', routing_key=f"Temesgen.{consumer}", body=message)

            self.build_widget(message)

class WindowManager(ScreenManager):
    pass
    
class ChatApp(MDApp):

    def build(self):
        return Builder.load_file('test.kv')
    
#if __name__ == "__main__":
#    ChatApp().run()

if __name__ == '__main__':
    ChatApp().run()
    p1 = Process(target=receive)
    p1.start()

.kv file

WindowManager:

    LoginScreen:

    SignupScreen:

    AddUserScreen:

    UsersScreen:

    CreateGroupScreen:

    InboxScreen:

<LoginScreen>:
    name: "login_screen"

    MDBoxLayout:
        orientation: "vertical"
        padding: 0,1
    
        MDCard:
            size_hint_y: .1
            line_color: (.95,.44,.49,1)
            spacing: 20
            padding: 20,10

            Widget:
                size_hint_y: .33

            MDLabel:
                halign: "center"
                text: "Login"
                font_style: "H4"
                theme_text_color: "Custom"
                text_color: (.95,.44,.49,1)

            Widget:
                size_hint_y: .33

        MDBoxLayout:
            orientation: "vertical"
            size_hint_y: .4
            spacing: 20
            
            MDTextField:
                id: uname_input
                pos_hint: {"center_x": .5}
                size_hint_x: None
                width: 250
                hint_text: 'Username'
                mode: 'rectangle'
                color: (0,1,1,1)
                line_color_normal: (0,1,0,1)
                text_color: (.95,.44,.49,1)

            MDTextField:
                id: pword_input
                pos_hint: {"center_x": .5}
                size_hint_x: None
                width: 250
                hint_text: 'Password'
                mode: 'rectangle'
                color: (0,1,1,1)
                line_color_normal: (0,1,0,1)
                text_color: (.95,.44,.49,1)

            MDTextButton:
                text: 'Sign Up'
                underline: True
                theme_text_color: "Custom"
                text_color: (.95,.44,.49,1)
                pos_hint: {'center_x': 0.5, 'center_y': 0.5}
                on_press: app.root.current = "signup_screen"

        MDBoxLayout:
            orientation: "vertical"
            size_hint_y: .5

            Widget:
                size_hint_y: .33

            MDRectangleFlatButton:
                text: "Login"
                font_size: 20
                text_color: (.95,.44,.49,1)
                line_color: (.95,.44,.49,1)
                pos_hint: {"center_x": .5, "center_y": .5}
                on_press: root.validate_user()

            Widget:
                size_hint_y: .6

<SignupScreen>:
    name: "signup_screen"

    MDBoxLayout:
        orientation: "vertical"
        padding: 0,1
    
        MDCard:
            size_hint_y: .1
            line_color: (.95,.44,.49,1)
            spacing: 20
            padding: 20,10

            MDBoxLayout:
                size_hint_x: .25

                MDIconButton:
                    size_hint_x: .25
                    icon: "keyboard-backspace"
                    pos_hint: {"center_x": .5, "center_y": .5}
                    on_press: app.root.current = "login_screen"

            MDLabel:
                size_hint_x: .5
                halign: "center"
                text: "Signin"
                font_style: "H4"
                theme_text_color: "Custom"
                text_color: (.95,.44,.49,1)

            MDBoxLayout:
                size_hint_x: .25

                Widget:
                    size_hint_y: .25
                    

        MDBoxLayout:
            orientation: "vertical"
            size_hint_y: .4
            spacing: 20
            
            MDTextField:
                id: uname_input
                pos_hint: {"center_x": .5}
                size_hint_x: None
                width: 250
                hint_text: 'Username'
                mode: 'rectangle'
                color: (0,1,1,1)
                line_color_normal: (0,1,0,1)
                text_color: (.95,.44,.49,1)

            MDTextField:
                id: pword_input1
                pos_hint: {"center_x": .5}
                size_hint_x: None
                width: 250
                hint_text: 'Password'
                mode: 'rectangle'
                color: (0,1,1,1)
                line_color_normal: (0,1,0,1)
                text_color: (.95,.44,.49,1)

            MDTextField:
                id: pword_input2
                pos_hint: {"center_x": .5}
                size_hint_x: None
                width: 250
                hint_text: 'Confirm Password'
                mode: 'rectangle'
                color: (0,1,1,1)
                line_color_normal: (0,1,0,1)
                text_color: (.95,.44,.49,1)

        MDBoxLayout:
            orientation: "vertical"
            size_hint_y: .5

            Widget:
                size_hint_y: .33

            MDRectangleFlatButton:
                text: "Signin"
                font_size: 20
                text_color: (.95,.44,.49,1)
                line_color: (.95,.44,.49,1)
                pos_hint: {"center_x": .5, "center_y": .5}
                on_press: root.signin()

            Widget:
                size_hint_y: .6

<UsersScreen>:
    name: "users_screen"

    MDBoxLayout:
        orientation: "vertical"
        padding: 0,1

        MDCard:
            size_hint_y: .1
            line_color: (.95,.44,.49,1)
            spacing: 20
            padding: 20,10

            MDIconButton:
                size_hint_x: .3
                icon: "logout"
                pos_hint: {"center_x": .5, "center_y": .5}
                on_press: app.root.current = "login_screen"

            MDLabel:
                halign: "center"
                text: "Users"
                font_style: "H4"
                theme_text_color: "Custom"
                text_color: (.95,.44,.49,1)

            MDBoxLayout:
                size_hint_x: .4

                MDIconButton:
                    icon: "account-plus"
                    pos_hint: {"center_x": .5, "center_y": .5}
                    on_press: app.root.current = "add_user_screen"

                MDIconButton:
                    icon: "account-multiple-plus"
                    pos_hint: {"center_x": .5, "center_y": .5}
                    on_press: app.root.current = "create_group_screen"

        MDBoxLayout:
            orientation: "vertical"
            size_hint_y: .9

            ScrollView:
                size: self.size
                pos: self.pos
                
                MDList:
                    id: user_field
                    spacing: 10
                    padding: 10

                    MDRectangleFlatButton:
                        text: 'User-1                                                           '
                        font_style: "H6"
                        text_color: (.95,.44,.49,1)
                        line_color: (.95,.44,.49,1)
                        pos_hint: {"center_x": .5, "center_y": .5}
                        on_press: root.to_inbox(self.text)

                    MDRectangleFlatButton:
                        text: 'User-2                                                           '
                        font_style: "H6"
                        text_color: (.95,.44,.49,1)
                        line_color: (.95,.44,.49,1)
                        pos_hint: {"center_x": .5, "center_y": .5}
                        on_press: root.to_inbox(self.text)

                    MDRectangleFlatButton:
                        text: 'User-3                                                           '
                        font_style: "H6"
                        text_color: (.95,.44,.49,1)
                        line_color: (.95,.44,.49,1)
                        pos_hint: {"center_x": .5, "center_y": .5}
                        on_press: root.to_inbox(self.text)

<CreateGroupScreen>:
    name: "create_group_screen"

    MDBoxLayout:
        orientation: "vertical"
        padding: 0,1

        MDCard:
            size_hint_y: .1
            line_color: (.95,.44,.49,1)
            spacing: 20
            padding: 20,10

            MDIconButton:
                id: remove_button
                size_hint_x: .2
                icon: "keyboard-backspace"
                pos_hint: {"center_x": .5, "center_y": .5}
                on_press: app.root.current = "users_screen"

            MDLabel:
                halign: "center"
                text: "Create Group"
                font_style: "H4"
                theme_text_color: "Custom"
                text_color: (.95,.44,.49,1)

        MDBoxLayout:
            orientation: "vertical"
            size_hint_y: .9
            padding: 20
            spacing: 40

            MDCard:
                size_hint_y: .1
                line_color: (.95,.44,.49,1)
                radius: 10
                padding: 25,3

                MDTextField:
                    id: username
                    pos_hint: {"center_x": .5, "center_y": .4}
                    size_hint_x: None
                    width: 250
                    hint_text: 'Username'

                MDIconButton:
                    id: add_button
                    size_hint_x: .2
                    icon: "account-multiple-plus"
                    pos_hint: {"center_x": .5, "center_y": .5}
                    on_press: root.add_user()

                MDIconButton:
                    id: remove_button
                    size_hint_x: .2
                    icon: "account-multiple-remove"
                    pos_hint: {"center_x": .5, "center_y": .5}
                    on_press: root.remove_all_users()

            MDCard:
                orientation: "vertical"
                size_hint_y: .6
                line_color: (.95,.44,.49,1)
                radius: 10

                MDLabel:
                    size_hint_y: .1
                    halign: "center"
                    text: "Add Users"
                    theme_text_color: "Custom"
                    text_color: (.95,.44,.49,1)

                MDBoxLayout:
                    size_hint_y: .9

                    ScrollView:
                        size: self.size
                        pos: self.pos
                    
                        MDList:
                            id: user_field
                            spacing: 10
                            padding: 10

            MDBoxLayout:
                orientation: "vertical"
                size_hint_y: .3

                Widget:
                    size_hint_y: .33

                MDRectangleFlatButton:
                    text: "Create Group"
                    text_color: (.95,.44,.49,1)
                    line_color: (.95,.44,.49,1)
                    pos_hint: {"center_x": .5, "center_y": .5}

                Widget:
                    size_hint_y: .33

<AddUserScreen>:
    name: "add_user_screen"

    MDBoxLayout:
        orientation: "vertical"
        padding: 0,1

        MDCard:
            size_hint_y: .1
            line_color: (.95,.44,.49,1)
            spacing: 20
            padding: 20,10

            MDIconButton:
                size_hint_x: .33
                icon: "keyboard-backspace"
                pos_hint: {"center_x": .5, "center_y": .5}
                on_press: app.root.current = "users_screen"

            MDLabel:
                halign: "center"
                text: "Add Users"
                font_style: "H4"
                theme_text_color: "Custom"
                text_color: (.95,.44,.49,1)

            Widget:
                size_hint_x: .33

        MDBoxLayout:
            orientation: "vertical"
            size_hint_y: .9
            padding: 20
            spacing: 40

            MDCard:
                size_hint_y: .1
                line_color: (.95,.44,.49,1)
                radius: 10
                padding: 25,3

                MDTextField:
                    id: username
                    pos_hint: {"center_x": .5, "center_y": .4}
                    size_hint_x: None
                    width: 250
                    hint_text: 'Username'

                MDIconButton:
                    id: add_button
                    size_hint_x: .2
                    icon: "account-multiple-plus"
                    pos_hint: {"center_x": .5, "center_y": .5}
                    on_press: root.append_user()

                MDIconButton:
                    id: remove_button
                    size_hint_x: .2
                    icon: "account-multiple-remove"
                    pos_hint: {"center_x": .5, "center_y": .5}
                    on_press: root.remove_all_users()

            MDCard:
                orientation: "vertical"
                size_hint_y: .6
                line_color: (.95,.44,.49,1)
                radius: 10

                MDLabel:
                    size_hint_y: .1
                    halign: "center"
                    text: "Add Users"
                    theme_text_color: "Custom"
                    text_color: (.95,.44,.49,1)

                MDBoxLayout:
                    size_hint_y: .9

                    ScrollView:
                        size: self.size
                        pos: self.pos
                    
                        MDList:
                            id: user_field
                            spacing: 10
                            padding: 10

            MDBoxLayout:
                orientation: "vertical"
                size_hint_y: .3

                Widget:
                    size_hint_y: .33

                MDRectangleFlatButton:
                    text: "Add Users"
                    text_color: (.95,.44,.49,1)
                    line_color: (.95,.44,.49,1)
                    pos_hint: {"center_x": .5, "center_y": .5}
                    on_press: root.add_user()

                Widget:
                    size_hint_y: .33

<InboxScreen>:
    name: "inbox_screen"

    MDBoxLayout:
        orientation: "vertical"
        padding: 0,1

        MDCard:
            size_hint_y: .1
            line_color: (.95,.44,.49,1)
            spacing: 20
            padding: 20,10

            MDIconButton:
                size_hint_x: .2
                icon: "keyboard-backspace"
                pos_hint: {"center_x": .5, "center_y": .5}
                on_press: app.root.current = "users_screen"

            MDLabel:
                id: header
                halign: "center"
                text: ""
                font_style: "H4"
                theme_text_color: "Custom"
                text_color: (.95,.44,.49,1)

        MDBoxLayout:
            orientation: "vertical"
            size_hint_y: .9

            ScrollView:
                size: self.size
                pos: self.pos
                MDList:
                    id: text_field
                    spacing: 10

        MDCard:
            size_hint_y: .075
            line_color: (.95,.44,.49,1)
            padding: 10,0

            MDTextField:
                id: message_input
                size_hint: None,None
                height: 100
                width: 325
                hint_text: 'Write'
                color: (0,1,1,1)
                line_color_normal: (.95,.44,.49,1)
                text_color: (.95,.44,.49,1)
                pos_hint: {"center_x": .5, "center_y": .3}

            MDIconButton:
                id: send_button
                icon: "send"
                pos_hint: {"center_x": .5, "center_y": .5}
                on_press: root.send()

2 Answers2

4

What you are pretending to do is to run kivy concurrently. The best choice for async events in kivy is to use asyncio (as the oficcial documentation suggest). First at all you have to be sure your app runs from asyncio.run() instead of App.run(). To do this, you have to import asyncio and also you have to add a method to your App class. See the example below:

import asyncio 

######## MAIN APP ######## 
class ExampleApp(App):
    def build(self):
        #Your  app stuff here

    async def kivyCoro(self):  #This is the method that's gonna launch your kivy app
        await self.async_run(async_lib='asyncio')
        print('Kivy async app finished...')

    # This func will start all the "tasks", in this case the only task is the kivy app
    async def base(self):
        (done, pending) = await asyncio.wait({self.kivyCoro()}, 
    return_when='FIRST_COMPLETED')

if __name__ == '__main__':
    instanceApp = ExampleApp() #You have to instanciate your App class
    asyncio.run(instanciaApp.base()) # Run in async mode

With the code above you'll be able to run tour kivy app as a task (concurrently).

So far, we just have run the kivy app in async mode (concurrently). To add other task inside the Kivy running loop:

import asyncio 

######## MAIN APP ######## 
class ExampleApp(App):
    def build(self):
        #Your  app stuff here

    async def kivyCoro(self):  #This is the method that's gonna launch your kivy app
        await self.async_run(async_lib='asyncio')
        print('Kivy async app finished...')

    async def task2InsideKivyLoop(self): #Here you declare the other task
        print('Another task running inside the kivy loop')
        await asyncio.sleep(1)


    # This func will start all the "tasks", in this case the only task is the kivy app
    async def base(self):
        (done, pending) = await asyncio.wait({self.kivyCoro(), task2InsideKivyLoop()}, 
    return_when='FIRST_COMPLETED')

if __name__ == '__main__':
    instanceApp = ExampleApp() #You have to instanciate your App class
    asyncio.run(instanciaApp.base()) # Run in async mode

As you can see, to add another task inside the KivyLoop we just have to declare it as a async method of the App class, then just add it to asyncio.wait()

If you want to run the other task outside the kivyLoop you have to do the fllowing:

import asyncio 

######## MAIN APP ######## 
class ExampleApp(App):
    def build(self):
        #Your  app stuff here

    async def kivyCoro(self):  #This is the method that's gonna launch your kivy app
        await self.async_run(async_lib='asyncio')
        print('Kivy async app finished...')

    # This func will start all the "tasks", in this case the only task is the kivy app
    async def base(self):
        (done, pending) = await asyncio.wait({self.kivyCoro()}, 
    return_when='FIRST_COMPLETED')


######### GLOBAL COROUTINE #######
# Here you can import functions from other python files or just declare a global func
async def GlobalTask():
    for i in range(10):
        print('Other concurrently global task... ',i)
        await asyncio.sleep(1)


if __name__ == '__main__':
        async def mainThread(): 
            instanceApp = ExampleApp() #Instanciate your App class
            a = asyncio.create_task(instanceApp.base()) #Run kivyApp as a task
            b = asyncio.create_task(GlobalTask()) #Run Global func as a task
            (done, pending) = await asyncio.wait({a}, return_when='FIRST_COMPLETED')
    asyncio.run(mainThread())

The examples above would let you do any async task during the kv loop wheter inside the kvLoop or not

Edher Carbajal
  • 534
  • 3
  • 8
-1

The code:

if __name__ == '__main__':
    ChatApp().run()
    p1 = Process(target=receive)
    p1.start()

will run your ChatApp, and then after you close the ChatApp, it will run the receive process. They will not run at the same time because the ChatApp().run() will not return until the ChatApp closes. Try changing the order to:

if __name__ == '__main__':
    p1 = Process(target=receive)
    p1.start()
    ChatApp().run()
John Anderson
  • 35,991
  • 4
  • 13
  • 36