Why are images not shown after threading in kivy app?

After making sure queues are processed in separate threads, my kivyapp doesn’t show the embedded image icons reliably anymore. weirdly sometimes they are shown, sometimes not. What is happening here?

Before threading some processes I had everything in the main thread. No problems with showing the images, however other processes like updating the labels according to commands in the queues didn’t work reliably. Now it’s the other way around.

This is my python code (shortened, ’cause character limitations):

Builder.load_file("gui/rebuilder.kv")

Window.size = (1024, 600)

    
# add stringproperty to class Image
class Image(Widget):
    #initialize StringProperty
    status : StringProperty = StringProperty(None)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.bind(status=self.on_status_change)

    def on_status_change(self, instance, value:str):
        # check if status changed 
        return value

# main thread for App
class MyGridLayout(Widget):
# global variables for pathing-thread and cubedetector-thread
    pathing: Pathing | None = None
    pathing_thread : Process = None
    cube_detection_thread: Process = None  

# initialize more variables, threads, etc.
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # queues
        self.queues: SystemQueues = SystemQueues()
        self.bilderkennung_to_gui = self.queues.gui_bilderkennung_rx
        self.gui_to_bilderkennung = self.queues.gui_bilderkennung_tx
        self.pathing_to_gui = self.queues.gui_pathing_rx
        self.gui_to_pathing = self.queues.gui_pathing_tx

        # variables
        self.time_started = False
        self.time_seconds = 0
        self.progress_started = False
        self.energy_started = False
        self.homing = False
        self.threads_running = True
        self.i = 1
        
        # create trigger for emergencies
        self.emergency_tracker = False
        self.emergency_count = 0

        # keep track of scheduled events
        self.scheduled: deque = deque()

        # check for emergencies
        self.schedule_interval(self.emergency_check,1)

        # start queues
        self.__internal_queue: Queue = Queue()
        queue_handler_thread: threading.Thread = 
            threading.Thread(target=self.queue_handler, daemon=True)
        queue_handler_thread.start()
        queue_pathing_handler_thread: threading.Thread = 
            threading.Thread(target=self.__gui_messenge_receiver, daemon=True)
        queue_pathing_handler_thread.start()
        queue_bilderkennung_handler_thread: threading.Thread = 
            threading.Thread(target=self.__bilderkennung_messenge_receiver, daemon=True)
        queue_bilderkennung_handler_thread.start()

        """# check for connectivity
        Clock.schedule_interval(self.wlan_check, 5)"""

        # initiate threads
        self.pathing: Pathing
        self.init_all()

    def init_all(self) -> None:
        MyGridLayout.pathing = PathingFactory.setup_pathing(self.queues)
        
        # initialize threads
        MyGridLayout.pathing_thread = Process(target=self.pathing.run)
        MyGridLayout.cube_detection_thread = Process(target=init_cube_detection, args=(self.queues, )) 
        MyGridLayout.pathing_thread.start()
        MyGridLayout.cube_detection_thread.start()

# multiple threads for messages between pathing, bildanalyse and gui --> avoid race condition
    def __gui_messenge_receiver(self) -> None:
        while True:
            try:
                get_pathing = self.pathing_to_gui.get()
                Logger.info(f"get pathing {get_pathing}")
            except Exception:
                get_pathing = None
            self.__internal_queue.put(get_pathing)

    def __bilderkennung_messenge_receiver(self) -> None:
        while True:
            try:
                get_bildanalyse = self.bilderkennung_to_gui.get()
                Logger.info(f"get bildanalyse {get_bildanalyse}")
            except Exception:
                get_bildanalyse = None
            self.__internal_queue.put(get_bildanalyse)

    def queue_handler(self):
        Logger.debug("Gui Queuehandler started")
        while True:
            # check for object in queue bildanalyse
            get_queue = None
            get_queue = self.__internal_queue.get()
            Logger.debug("Reveived from int queue")
            Logger.info(f"get_queue = {get_queue}")
            

            """# check for activity in pathing queue
            prev_get_pathing = None
            if get_pathing != prev_get_pathing:
                Logger.info(get_pathing)"""

            # check for object in queue bildanalyse
            """# check for activity in bildanalyse queue
            prev_get_bildanalyse = None
            if get_bildanalyse != prev_get_bildanalyse:
                Logger.info(get_bildanalyse)"""
            try:    
                if isinstance(get_queue, Setup):
                    self.home_update(get_queue)
                elif isinstance(get_queue, MeasureResults):
                    self.energy_update(get_queue)
                elif isinstance(get_queue, Build):
                    self.start_update_pathing(get_queue)
                elif isinstance(get_queue, Analyse):
                    self.start_update_bildanalyse(get_queue)
                elif isinstance(get_queue, Reset):
                    self.stop_processes_update(get_queue)
                elif isinstance(get_queue, Emergency):
                    self.emergency(get_queue)
            except Exception as e:
                print(f"Fehler aufgetreten {e}")

# processes while homing 3D Rebuilder
    def home_rebuilder(self):
        command = Setup("home")
        self.gui_to_pathing.put(command)
                        
        # update progress label
        self.update_progress_label("Gerät wird kalibriert...")
        self.schedule_event(self.clear_progress_bar, 0)
                  
    def home_update(self, get_pathing: Setup):
        # progress update homing
        if get_pathing.ans == "ack":
            self.update_progress_label("Gerät wird kalibriert...")
        
            zeiten = [0.1, 1.5, 2.2, 3.9, 5.2, 6.7, 8, 9.4, 10.7]
            for zeit in zeiten:
                self.schedule_event(self.update_progress_bar, zeit)
                  
        elif get_pathing.ans == "done":
            self.ids.progress_label.text = "Gerät ist betriebsbereit."
            self.schedule_event(self.update_progress_bar, 0)
    
            #Start-Button aktivieren
            self.ids.start.disabled = False
            self.ids.reset.disabled = False

            #Anzeigeelemente aktivieren
            self.ids.timer.status = "on"
            self.ids.strom.status = "on"

# starting 3D-Rebuilding process
    def start(self):
        #Clear Progress_bar
        self.schedule_event(self.clear_progress_bar, 0)
        #Startbefehl an Pathing für Strommessung
        command = Control("start")
        self.gui_to_pathing.put(command)
        self.gui_to_bilderkennung.put(command)
        
        # Update progress label
        if self.progress_started == False:
            self.progress_started = True
                    
            # update label, progressbar
            self.update_progress_label("Bildanalyse initialisieren")
            self.schedule_event(self.update_progress_bar, 0)

            # start timer
            self.time_started = True
            self.schedule_interval(self.zeit_update, 0)
            
            # start energy update
            self.energy_started = True
            self.schedule_interval(self.energy_ask, 1.5)
            
            # Deactivate start, reset
            self.ids.start.disabled = True
            self.ids.reset.disabled = True
            self.ids.stop.disabled = False

    def start_update_bildanalyse(self, get_bildanalyse: Analyse): 
        if self.progress_started:
            if get_bildanalyse:
                #updates = ["Quadrant erkannt", "Bilderkennung..", "Bilderkennung..", "Validierung", "Bilderkennung abgeschlossen"]
                if get_bildanalyse.quadrant == "quadrant":
                    self.update_progress_label("Quadrant erkannt")
                    self.schedule_event(self.update_progress_bar, 0)
                elif get_bildanalyse.first_side == "first_side":
                    self.update_progress_label("Bilderkennung..")
                    self.schedule_event(self.update_progress_bar, 0)
                elif get_bildanalyse.second_side == "second_side":
                    self.schedule_event(self.update_progress_bar, 0)
                elif get_bildanalyse.validate == "validate":
                    self.update_progress_label("Validierung")
                    self.schedule_event(self.update_progress_bar, 0)
                elif get_bildanalyse.client_closed == "client_closed":
                    self.schedule_event(self.update_progress_bar, 0)

    def start_update_pathing(self, get_pathing: Build):
        if self.progress_started:
            if get_pathing:
                if get_pathing.ready == "ready":
                    self.update_progress_label("Pfadberechnung")
                    self.schedule_event(self.update_progress_bar, 0)
                elif get_pathing.first_side == "first_side":
                    self.update_progress_label("Nachbau..")
                    self.schedule_event(self.update_progress_bar, 0)
                elif get_pathing.second_side == "second_side":
                    self.schedule_event(self.update_progress_bar, 0)
                elif get_pathing.done == "done":
                    # update label
                    self.schedule_event(self.update_progress_bar, 0)
                    self.update_progress_label("3D rebuilding abgeschlossen.")
                    # play sound
                    radio_jingle: SoundEffect = SoundEffect("Common/Audio/radio-jingle.wav")
                    radio_jingle.play()
                    
                    # make sure progressbar is fully updated
                    rest = 11-self.i
                    Logger.info(f"rest = {rest}")
                    for r in range(rest):
                        Logger.info(f"self.i = {self.i}")
                        self.schedule_event(self.update_progress_bar, 0.1*r)

                    # stop processes
                    self.stop_label_update()
                    self.stop_all_processes()


# stopping 3D - rebuilding --> this process is reached through: end of normal process, press stop button, after emergencies                         
    def stop_label_update(self):
        # stop timer, progress and energy update
        self.progress_started = False
        self.time_started = False
        self.energy_started = False
                    
    def stop_all_processes(self):
        # send delete message to pathing, bildanalyse --> initiate stop/reset of processes
        command = Reset("delete")
        self.gui_to_bilderkennung.put(command)
        self.gui_to_pathing.put(command)

        # set thread status to false
        self.threads_running = False
        
        # deactivate stop button
        self.ids.stop.disabled = True
    
    def stop_processes_update(self, get_pathing: Reset):
        #Answer from pathing
        if get_pathing.ans == "ack":
            # activate reset button
            self.ids.reset.disabled = False
            self.update_progress_label("Vor dem Reset Würfel entfernen.")
            # unschedule Clock-events
            self.cancel_scheduled()

# stop button
    def stop_button(self):
        # stop updates on gui
        self.stop_label_update() 
        # initiate stop of all processes
        self.stop_all_processes()
        # update label
        self.update_progress_label("Prozesse werden angehalten.")

# process for reset
    def reset(self, *args):
        # initiate new threads
        if not self.threads_running:
            MyGridLayout.pathing = None
            MyGridLayout.pathing_thread: Process = None
            MyGridLayout.cube_detection_thread: Process = None
            
            bilderkennung_to_pathing = self.queues.bilderkennung_pathing_rx
            pathing_to_bilderkennung = self.queues.bilderkennung_pathing_tx

            self.empty_queue(bilderkennung_to_pathing)
            self.empty_queue(pathing_to_bilderkennung)
            self.empty_queue(self.bilderkennung_to_gui)
            self.empty_queue(self.gui_to_bilderkennung)
            self.empty_queue(self.pathing_to_gui)
            self.empty_queue(self.gui_to_pathing)

            self.init_all()
            self.threads_running = True
        
        Logger.info(f"Threads {self.threads_running}")
        # update label
        self.ids.progress_label.text = "Gerät zurücksetzen"
        self.schedule_event(self.clear_progress_bar, 0)
        Logger.info(f"scheduled stack {self.scheduled}")
        
        # reset counter, labels, timer
        self.i = 1
        self.time_seconds = 0
        self.ids.energy.text = "0.0 Ws"
        self.ids.strom.status = "off"
        self.ids.timer.status = "off"

        # deactivate buttons
        self.ids.reset.disabled = True
        self.ids.start.disabled = True
    
        self.home_rebuilder()

    # empty queues for reset
    def empty_queue(self, queue: Queue):
        while not queue.empty():
            queue.get()
    
    """def wlan_check(self, dt):
        connected = ConnectivityTest.test_internet_connectivity()
        if connected:
            image_path = "gui/images/wlan_on.png"
        else:
            image_path = "gui/images/wlan_off.png"
        self.ids.wlan.source = image_path"""

#Build App as usual, left out 'cause character limitations

This is my kv-file:

#:import Factory kivy.factory.Factory

<Label>
    font_size: 28
    color: (.75,.75,.75,.7)

<Button>
    background_color: 0,0,0,0
    font_size: 24
    color: (0,.6,.9,.8) if self.state == "normal" else (0,.6,.9,1)
    text_size: self.width, self.height
    halign: "center"
    valign: "middle"

<RoundedButton@Button>
    #padding: 0,0,90,0
    canvas.before:
        Color:
            rgba: (0, .6, .9, .8) if self.disabled == False else (.2,.2,.2,1)
        RoundedRectangle:
            size: self.size
            pos: self.pos
            radius: [5,]
        
        Color:
            rgba: (.05, .05, .05, 1)
        RoundedRectangle:
            size: self.width-4, self.height-4
            pos: self.x+2, self.y+2
            radius: [5,]
        Color:
            rgba: (0, 0, 0, 0) if self.state == "normal" else (0,.6,.9,.6)
        RoundedRectangle:
            size: self.size
            pos: self.pos
            radius: [5,]
    
<Cube>
    status: "normal"
    canvas:
        Color:
            rgba: (.14, .14, .14, 1) if self.status == "normal" else (0,.5, .8, .8)
        RoundedRectangle:
            size: self.size
            pos: self.pos
            radius: [5, ]
            
#Popups definieren
<StartupPopup@Popup>
    id: startup_popup
    grid_layout_instance: None
    auto_dismiss: False
    #size_hint: 0.9,0.9
    pos_hint: {"center_x":0.5, "center_y":0.5}
    background_color: (0.05, .05, .05, 1)

    title: ""
    separator_height: 0
    
    GridLayout:
        cols: 1
        size: root.width, root.height
        padding: 130, 0, 130, 80

        BoxLayout:
            orientation: "vertical"
            padding: 0, -150, 0, 0
        Image:
            source: "gui/images/cube_3d_b.png"
        
        Label:
            font_size: 24
            text_size: root.width, None
            size: self.texture_size
            #padding: 10, 10, 10, 70
            text: "Willkommen bei 3DRebuilder."
            halign: "center"
            valign: "center"

        GridLayout:
            cols: 1
            row_default_height: 100
            row_force_default: True
            padding: 130, 0, 130, 0
            
            RoundedButton:
                text: "Los gehts!"
                halign: "center"
                valign: "center"
                #padding: 0, 0, 0, 0
                on_release: 
                    root.dismiss()
                    app.root.home_rebuilder()

<EmergencyPopup@Popup>
    id: emergency_popup
    grid_layout_instance: None
    auto_dismiss: False
    size_hint: 0.8,0.8
    pos_hint: {"x":0.1, "top":0.9}
    #background_color: (0.05, .05, .05, 1)

    title: "Notaus"
    
    GridLayout:
        cols: 1
        size: root.width, root.height
        padding: 50, 10, 80, 10

        Label:
            id: popup_label
            text_size: self.size
            #size: self.texture_size
            bg_color: (1, .2, 0, 1)
            #padding: 10
            text: "Der Notaus wurde gedrückt. n 3DRebuilder überprüfen und verkeilte Teile entfernen."
            halign: "center"
            valign: "center"

        GridLayout:
            cols: 1
            row_default_height: 100
            row_force_default: True
            padding: 80, 0, 80, 0
            
            RoundedButton:
                text: "Problem behoben"
                halign: "center"
                padding: 0,0,0,0
                on_release: 
                    #root.dismiss()
                    app.root.close_em_popup()
            
#Layout GUI
<MyGridLayout>  
    #Contains all the other rows
    GridLayout
        cols:1
        size: root.width, root.height
        padding: 30, 50, 30, 0 #Padding Hauptframe

        BoxLayout:
            orientation: "vertical"
            #Logo and Wifi-Elements
            GridLayout:
                id: row_4
                cols: 3

                GridLayout:
                    cols: 1
                    
                    Image:
                        source: "gui/images/logo_b.png"
                        size_hint_y: None  # Tells the layout to ignore the size_hint in y dir
                        height: dp(70)  # The fixed height you want
                
                GridLayout:
                    cols: 1
                    
                GridLayout:
                    cols: 2
                
                    BoxLayout:
                        orientation: 'horizontal'

                        # Align the content to the right within the BoxLayout
                        padding: (160, 35, 80, 0)  # Padding to the left to keep space
                        
                        Image:
                            id: wlan
                            source: "gui/images/wlan_on.png"
                            size_hint_y: None  # Tells the layout to ignore the size_hint in y dir
                            height: dp(35)  # The fixed height you want
                            pos_hint: {"center_x":1, "center_y":1}
        
                        #Image:
                            #source: "gui/images/empfang_3.png"
                            #size_hint_y: None  # Tells the layout to ignore the size_hint in y dir
                            #height: dp(25)  # The fixed height you want
                            #pos_hint: {"center_x":.7, "center_y":1}
                        
            #Row for Buttons
            GridLayout:
                id: row_1
                cols: 3
                padding: 30, -40, 30, 0
                spacing: 5

                row_default_height: 100
                row_force_default: True

                #Start Button
                RoundedButton:
                    id: start
                    name: start
                    text: "Start"
                    disabled: True
                    on_release:
                        root.start()

                    Image:
                        source: "gui/images/start.png" if self.disabled == False else "gui/images/start_off.png"
                        center_x: self.parent.center_x-55
                        center_y: self.parent.center_y
                        size_hint_y: None
                        height: dp(40)
                    
                #Stop Button
                RoundedButton:
                    id: stop
                    text: "Stop"
                    disabled: True
                    on_release:
                        root.stop_button()

                    Image:
                        source: "gui/images/stop.png" if self.disabled == False else "gui/images/stop_off.png"
                        center_x: self.parent.center_x-55
                        center_y: self.parent.center_y
                        size_hint_y: None
                        height: dp(40)
                    
                #Reset Button
                RoundedButton:
                    id: reset
                    text: "Reset"
                    disabled: True
                    on_release:
                        root.reset()

                    Image:
                        source: "gui/images/reset.png" if self.disabled == False else "gui/images/reset_off.png"
                        center_x: self.parent.center_x-55
                        center_y: self.parent.center_y
                        size_hint_y: None
                        height: dp(40)

            #Gridlayout for visual feedback elements
            GridLayout:
                id: row_3
                cols: 2
                padding: 30, 0
                
                #Progress feedback
                GridLayout:
                    cols: 1
                    rows: 2
                    spacing: 10
                    
                    row_default_height: 100
                    row_force_default: True

                    BoxLayout:
                        padding: 0,30,0,0
                        Label:
                            id: progress_label
                            text_size: root.width, None
                            size_hint: 1, 1
                            size: self.texture_size
                            text: "Gerät vorbereiten"
                            halign: "center"
                            pos_hint: {"y":.75}

                    #Progress-Cubes
                    BoxLayout:
                        id: cubecontainer
                        orientation: "horizontal"
                        size_hint: 1, 1
                        padding: 60, -10, 0, 0
                        spacing: 5

                        Cube:
                            id: p1
                            size_hint: None, None
                            size: 30,30
                            pos_hint: {"center_x": 1, "center_y": 1}

                        Cube:
                            id: p2
                            size_hint: None, None
                            size: 30,30
                            pos_hint: {"center_x": 1, "center_y": 1}

                        Cube:
                            id: p3
                            size_hint: None, None
                            size: 30,30
                            pos_hint: {"center_x": 1, "center_y": 1}

                        Cube:
                            id: p4
                            size_hint: None, None
                            size: 30,30
                            pos_hint: {"center_x": 1, "center_y": 1}
                        
                        Cube:
                            id: p5
                            size_hint: None, None
                            size: 30,30
                            pos_hint: {"center_x": 1, "center_y": 1}
                        
                        Cube:
                            id: p6
                            size_hint: None, None
                            size: 30,30
                            pos_hint: {"center_x": 1, "center_y": 1}

                        Cube:
                            id: p7
                            size_hint: None, None
                            size: 30,30
                            pos_hint: {"center_x": 1, "center_y": 1}

                        Cube:
                            id: p8
                            size_hint: None, None
                            size: 30,30
                            pos_hint: {"center_x": 1, "center_y": 1}
                        
                        Cube:
                            id: p9
                            size_hint: None, None
                            size: 30,30
                            pos_hint: {"center_x": 1, "center_y": 1}
                        
                        Cube:
                            id: p10
                            size_hint: None, None
                            size: 30,30
                            pos_hint: {"center_x": 1, "center_y": 1}

                #Timer and Energy-Update
                GridLayout:
                    cols: 1
                    rows: 2
                    spacing:10
                    padding: 0, -60, 40, 0
                    row_default_height: 100
                    row_force_default: True

                    GridLayout:
                        cols: 1

                        BoxLayout:
                            orientation: "vertical"
                            size_hint: (1, None)
                            height: dp(30)
                            padding: (0,0,185,-37)
                    
                            Image:
                                id: timer
                                source: "gui/images/timer.png" if self.status == "on" else "gui/images/timer_off.png"
                                status: "off"
                                size_hint_y: None  # Tells the layout to ignore the size_hint in y dir
                                height: dp(30)  # The fixed height you want

                        BoxLayout:
                            orientation: "vertical"
                            size_hint: (1, 1)
                            padding: (0,0,0,30)
                            Label:
                                id: zeit
                                text: "00:00:00"
                    
                    GridLayout:
                        cols: 1


                        BoxLayout:
                            orientation: "vertical"
                            size_hint: (1, None)
                            height: dp(30)
                            padding: (0,0,180,-39)
                            Image:
                                id: strom
                                status: "off"
                                source: "gui/images/energy.png" if self.status == "on" else "gui/images/energy_off.png"
                                size_hint_y: None  # Tells the layout to ignore the size_hint in y dir
                                height: dp(30)  # The fixed height you want

                        BoxLayout:
                            orientation: "vertical"
                            size_hint: (1, 1)
                            padding: (0,0,0,25)
                            Label:
                                id: energy
                                text: "0.0 Ws"

            RelativeLayout: 
                Label:
                    font_size: 20
                    text: "Gruppe 30"
                    pos_hint: {"center_x": .15, "center_y": .4}

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật