플러터 앱을 개발하다보면 제목과 같은 이슈를 자주 보게됩니다. 이 이슈는 보통 Colum/Row를 사용하여 위젯들을 배치할 때, 자식 위젯에 크기에 대한 제약(Constraint)이 없어 자식 위젯이 부모 위젯의 크기보다 커지려고 할 때 발생합니다.
이러한 이슈는 보통 자식 위젯을 Expanded/Flexible로 감싸주면 해결됩니다. 그 이유는 Expanded/Flexible이 부모 위젯에서의 남는 공간을 분배해서 자식 위젯이 가질 너비와 높이를 정해주기 때문입니다.
여기서 Expanded/Flexible을 사용하면 대부분 해결된다 에서 그치지 않고, 부모 위젯이 자식 위젯에게 내려주는 제약 조건에 대한 background를 알면 좀 더 이해에 도움이 될 것 같습니다. 이 제약 조건을 이해하기 위해 먼저 플러터의 트리 구조에 대해서 간략하게 설명드리겠습니다.
플러터는 화면을 효율적으로 렌더링하기 위해 위젯 트리 - 엘리먼트 트리 - 렌더 트리 이렇게 총 3가지의 트리를 사용합니다. 우리(개발자)가 직접 작성하는 코드가 위젯 트리가 되며, 위젯 트리와 1대1로 매칭되는 것이 엘리먼트 트리, 그리고 실제로 화면에 보여지는 것이 렌더 트리가 됩니다.
위젯 트리는 불변합니다. 따라서 우리가 코드를 변경하고, 변경사항을 화면으로 확인하기 위해 hot reload를 하면, 기존 위젯 트리는 폐기되고 build() 함수가 호출되며 위젯 트리가 재생성됩니다. 그러면 이 경우, 엘리먼트 트리와 렌더 트리도 리빌드가 될까요?
만약 모든 트리가 리빌드가 된다면 화면을 렌더링하는데 드는 비용이 생각보다 클 것입니다. 실제로 엘리먼트 트리와 렌더 트리는 폐기되지 않습니다. 위젯 트리가 리빌드 될 때, 엘리먼트 트리는 이전의 위젯 트리와 새로 생성된 위젯 트리를 위젯 타입과 key 값으로 비교합니다. 그리고 변경된 부분에 한해서만 렌더 트리를 업데이트 합니다. 정확히는 RenderObjectElement가 RenderObject를 업데이트하면서 렌더 트리가 갱신되는 것이죠.
플러터는 이때 렌더 트리를 DFS 방식으로 순회하면서, 부모 위젯의 제약을 자식 위젯으로 내려줍니다. 그러면 자식 위젯은 상속받은 제약에 한해서만 확장될 수 있게 됩니다.
To perform layout, Flutter walks the render tree in a depth-first traversal (DFS) and passes down size constraints from parent to child. In determining its size, the child must respect the constraints given to it by its parent. Children respond by passing up a size to their parent object within the constraints the parent established.
그런데 만약 부모 위젯에서 제약을 내려주지 않은 경우, 자식 위젯은 부모 위젯보다 커지려고 하고 이때 부모 위젯보다 커지면서 overflow 이슈가 발생하게 됩니다.
따라서 이 경우 Expanded/Flexible 위젯으로 자식 위젯을 감싸주면 Expanded/Flexible이 부모 위젯 내에서의 남는 공간을 분배하여 자식 위젯이 가질 크기를 강제하기 때문에 이슈를 해결할 수 있습니다.
https://docs.flutter.dev/testing/common-errors#a-renderflex-overflowed
https://docs.flutter.dev/resources/architectural-overview#layout-and-rendering
https://velog.io/@jeongminji4490/Flutter-Flexible-Expanded