Title: Kivy (Python): How to change the BoxLayout height based on its children?
Tags: python;user-interface;kivy
Question: Im writing a news app in Kivy that takes articles from a front page of a website and makes them viewable in the app. Depending on the articles length the size and number of paragraphs may vary. Since it uses a BS4 webscraper to exctract the contents of a website, the whole gui programming takes place in python, not in .kv language. So, my question is, how can I stop the Image from reserving space on screen (I want the labels to always be under it) and how can I change the height of a BoxLayout based on its children so there is enough space for all of them, but not too much.
Here is a simplified version of my code that demonstrates my problem. In this example the height of a BoxLayout is set to 1000dp which is fine for roughly 4 paragraphs but if there were to be more it would become to cluttered. Is there a way to avoid this problem? Any help will be useful.
from kivymd.app import MDApp
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.label import MDLabel
from kivy.uix.screenmanager import Screen
from kivy.uix.scrollview import ScrollView
from kivy.uix.image import AsyncImage
class RootWidget(Screen):
def __init__(self, **kw):
super().__init__(**kw)
self.update()
def update(self):
paragraphs_text = ["Lorem ipsum dolor sit amet,", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut"
" labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco "
"laboris nisi ut aliquip ex ea commodo consequat.", "Duis aute irure dolor in reprehenderit in"
" voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat "
"non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."]
header_text = "Lorem ipsum dolor sit amet"
header_image_src = "images/Cariboo_Peaks.jpg"
date_of_publishing_text = "03.05.2020"
scroll_widget_holder = ScrollView()
widget_holder = BoxLayout(orientation="vertical", size_hint_y=None, padding=5)
# minimum_height doesnt work in python, only in .kv language so is there a way to get the height automatically?
# 1000dp works fine for this amount of paragrpahs but i there was twice as many as there are now it would all
# become to cludderd and the image would be very small
# on the other hand, if I make this number too big the wigets are too far apart from each other
# So how can I fix this?
widget_holder.height = "1000dp"
header_image = AsyncImage(source=header_image_src, size_hint=(1, 1),
allow_stretch=False, keep_ratio=True)
header_label = MDLabel(text=header_text, size_hint_y=None, font_style="H6")
# Thats what I came up with, if there is a better way of automatically asigning the height of a widget based
# on its contents please let me know
header_label.height = str(len(header_label.text)) + "dp"
# This label is supossed to be right unter the header label(see the screens below to see my go to result created
# in photoshop)
header_date_label = MDLabel(text=date_of_publishing_text, size_hint_y=None, font_style="Caption", height="10dp")
widget_holder.add_widget(header_image)
widget_holder.add_widget(header_label)
widget_holder.add_widget(header_date_label)
for paragraph in paragraphs_text:
text_paragraph = MDLabel(text=paragraph, size_hint_y=None,
pos_hint={"center_x": .5}, halign="justify")
text_paragraph.height = str(len(text_paragraph.text)) + "dp"
widget_holder.add_widget(text_paragraph)
scroll_widget_holder.add_widget(widget_holder)
self.add_widget(scroll_widget_holder)
class TestApp(MDApp):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()
Comment: Well, Im using a for loop to go through all the news on this website and as far as I know, you can’t dynamically add widgets using the .kv language.

Comment: I tried it but it raised more problems than it solved. The code that I have right know is very close to working, I just need to figure out how to asign the height of the BoxLayout based on it's children.

Comment: How does the webscraper prevent you from using the `kv` language?

Comment: But you can create the `ScrollView` and `BoxLayout` in `kv`, which makes it easier to change the height of  `BoxLayout` based on its children. Then you can add `MDLabels` to the `BoxLayout` in Python. Would that sort of arrangement work?

Here is the accepted answer: You can calculate the height of the BoxLayout yourself by just adding the heights of all its children (including padding and spacing). If you know (or set) the heights of the children as you add them during the update method, you can just set the height using:
widget_holder.height = new_height
where new_height is the above calculated sum. If you don't know the heights of the children, you can do something like:
def adjust_height(self, box, dt):
# this does not handle padding and spacing
new_height = 0
for child in box.children:
new_height += child.height
box.height = new_height
And you can call it at the end of your update() method as:
Clock.schedule_once(partial(self.adjust_height, widget_holder))
Comment for this answer: This work fine until you resize to window to be the size of a smartphone. Than the text stars to overlap and becomes unreadable. Is there a way to increase spacing beetwen those labels and how to stick this Image to the top, so its top always touches the top of the screen no matter what size it is?
Comment for this answer: You can add the `spacing` parameter to your `BoxLayout`. I think the `AsyncImage` should be at the top, since it is added first. However, if you are calculating the size of the `BoxLayout` by summing the height of its children, then you can't specify the `height` of any of the children using `size_hint`, since that make the `height` of the child dependent of the `height` of the `BoxLayout`. That's a circular dependency.


{'source': 'StackExchangeDataset', 'subset': 'stackoverflow.com', 'metadata': {'AnswerCount': 1, 'CommentCount': 4, 'ContentLicense': 'CC BY-SA 4.0', 'CreationDate': '2020:00:05/03/20', 'FavoriteCount': None, 'PostTypeId': 1, 'Tags': '<python><user-interface><kivy>', 'Title': 'Kivy (Python): How to change the BoxLayout height based on its children?', 'ViewCount': 528, 'id': 61575386, 'user_AboutMe': '<p>18 yo from Poland, a selfthought programmer and web developer and 3D artist.</p>\n', 'user_AccountId': 17222128.0, 'user_DisplayName': 'GawronDev', 'user_Reputation': 117.0, 'user_Views': 21.0, 'user_id': 12468673.0}}