KivyMD: Automatic resizing of MDBoxLayout and MDLabel based on text length

Question:

I’m trying to make a simple chat that auto-sizes the message box to fit text of different lengths.
For some reason, when the text is not very long, everything works well. But with increasing text length in the field and its transition to the second line, the program doesn’t work correctly.

I know that the problem is in adaptive_height of MDBoxlayout and MDLabels, and conflicts between them, but I don’t know how to make this work without it, and what’s going on in specific, when algorithms go crazy.

main.py:

from kivy.config import Config

Config.set('graphics', 'resizable', 0)
Config.set("graphics", "width", 360)
Config.set("graphics", "height", 740)

from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.properties import BooleanProperty, StringProperty, ListProperty
from kivymd.uix.boxlayout import MDBoxLayout


class MesApp(MDApp):
    chat_logs = ListProperty()

    def build(self):
        return Builder.load_file('main.kv')

    def send(self, text):
        self.chat_logs.append(
            {"text": text if text else 'empty message',
             "send_by_user": True,
             "time": "21.10.2022 13:11",
             "pos_hint": {"right": 1},
             }
        )
        self.receive()

    def receive(self):
        self.chat_logs.append(
            {
                "text": 'Answer',
                "time": '21.10.2022 14:30',
                "send_by_user": False,
            }
        )


Builder.load_string(
    """
<Chat>:

    adaptive_height: True
    padding: [dp(8), dp(8)]
    orientation: 'vertical'

    canvas.before:
        Color:
            rgba:
                app.theme_cls.primary_light if self.send_by_user 
                else app.theme_cls.primary_dark
        RoundedRectangle:
            size: self.size
            pos: self.pos
            radius:
                [dp(8), dp(8), (dp(-5), dp(5)), dp(8)] if self.send_by_user 
                else [(dp(-5), dp(5)), dp(8), dp(8), dp(8)]

    MDLabel:
        text: root.text
        adaptive_height: True

    MDLabel:
        text: root.time
        font_size: '12sp'
        theme_text_color: 'Secondary'
        adaptive_height: True

""")


class Chat(MDBoxLayout):
    send_by_user = BooleanProperty()
    time = StringProperty()
    text = StringProperty()


if __name__ == '__main__':
    MesApp().run()

main.kv:

<ScreenM>:
MDBoxLayout:
    orientation: 'vertical'
    padding: dp(5), dp(5)
    RecycleView:
        data: app.chat_logs
        viewclass: 'Chat'
        RecycleBoxLayout:
            padding: dp(10)
            spacing: dp(15)
            orientation: "vertical"
            size_hint_y: None
            height: self.minimum_size[1]
            default_size_hint: 0.8, None
            default_size: None, None

    TextInput:
        id: field
        hint_text: "Write your message"
        multiline: True
        cursor_color: app.theme_cls.primary_color
        padding: dp(7)
        size_hint_y: None
        height: dp(35)

    MDIconButton:
        icon: "send-circle-outline"
#        user_font_size: '50'
        pos_hint: {"center_x": .5}
        icon_size: "50sp"
        on_release: app.send(field.text)

Some of the warnings:

[CRITICAL] [Clock       ] Warning, too much iteration done before the next frame. Check your code, or increase the Clock.max_iteration attribute. Remaining events:
<ClockEvent (-1.0) callback=<bound method BoxLayout.do_layout of <__main__.Chat object at 0x0000029E9552ABA0>>>
<ClockEvent (-1.0) callback=<bound method BoxLayout.do_layout of <__main__.Chat object at 0x0000029E95594BA0>>>
<ClockEvent (-1.0) callback=<bound method Label.texture_update of <kivymd.uix.label.label.MDLabel object at 0x0000029E955A05F0>>>
<ClockEvent (-1.0) callback=<bound method RecycleViewBehavior.refresh_views of <kivy.uix.recycleview.__init__.RecycleView object at 0x0000029E92E3A6D0>>>

kivy==2.1.0
kivymd==1.1.1

Asked By: Kreol

||

Answers:

There seems to be an interaction between the MDBoxLayout and the MDLabels within it causing an endless loop. You can eliminate the adaptive_height from the Chat rule with a simple calculation of its height. Here is a modified version of that <Chat> rule:

<Chat>:
    size_hint_y: None
    height: lab1.height + lab2.height + self.padding[1] + self.padding[3] + self.spacing  # calculate height
    padding: [dp(8), dp(8)]
    orientation: 'vertical'

    canvas.before:
        Color:
            rgba:
                app.theme_cls.primary_light if self.send_by_user 
                else app.theme_cls.primary_dark
        RoundedRectangle:
            size: self.size
            pos: self.pos
            radius:
                [dp(8), dp(8), (dp(-5), dp(5)), dp(8)] if self.send_by_user 
                else [(dp(-5), dp(5)), dp(8), dp(8), dp(8)]

    MDLabel:
        id: lab1  # id added
        text: root.text
        adaptive_height: True

    MDLabel:
        id: lab2  # id added
        text: root.time
        font_size: '12sp'
        theme_text_color: 'Secondary'
        adaptive_height: True
Answered By: John Anderson
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.