[Error] Vertical viewport was given unbounded height / RenderBox was not laid out.
daehwi 2023. 4. 4.오류 예시
- Vertical viewport was given unbounded height.
- RenderBox was not laid out.
- Cannot hit test a render box that has never been laid out.
Flutter로 UI를 작성하다보면 위의 오류들과 같거나 또는 거의 비슷한 에러들이 종종 발생한다.
나의 경우 위의 세가지 에러가 동시에 발생했다.
그래서 오늘은 이 오류들의 원인과 해결법에 대해서 알아보고자 한다.
원인
먼저, Vertical viewport was given unbounded height
오류 밑의 설명을 읽어보자.
Viewports expand in the scrolling direction to fill their container. In this case, a vertical viewport was given an unlimited amount of vertical space in which to expand. This situation typically happens when a scrollable widget is nested inside another scrollable widget.
이를 통해 Viewport가 Container를 채우기 위해 스크롤 방향으로 확장되었고, 이 경우에 Vertical viewport가 무한대의 공간을 차지해 오류가 발생함을 알 수 있다.
또한, Scrollable widget안에 직접적으로 다른 Scrollable widget을 넣은 경우 이 오류가 주로 발생한다고 친절하게 알려주고 있다.
그렇다면, 여기서 Viewport, Container, Scrollable widget이 정확히 어떤 것을 의미하는 걸까? 밑의 예시코드를 통해 알아보자.
예시코드
위의 코드에서는 Column안에 5개의 item을 가진 ListView가 속해있다.
이 경우에 Vertical viewport was given unbounded height
오류가 발생하고, 우리가 해석했던 오류 메세지를 마주할 수 있다.
📌 Listview/Gridview 의 동작 방식
먼저, 우리가 스크롤을 위해 자주 사용하는 Listview나 Gridview의 동작방식을 살펴보자.
이 위젯들은 제약조건이 걸릴 때까지 자신을 확장한 후, 그 범위를 넘어가는 항목을 스크롤 할 수 있도록 동작한다.
이 말은, 부모 위젯 또는 기타 제약조건이 허용하는 범위 내에서 스스로의 크기를 최대로 설정한다는 의미이다.
왜냐하면, 확장을 통해 자신의 크기를 확실히 설정해야 어떤 항목이 범위를 벗어나 스크롤이 필요한지 판단할 수 있기 때문이다.
그러나 위의 코드를 보면 Listview는 부모위젯인 Column안에 속해있지만, Column은 자식 위젯인 Listview에게 어떠한 제약조건도 걸지 않았으므로, Listview가 무한대로 확장되며 오류가 발생하는 것이다.
해결방법
1. SizedBox 사용
첫번째 방법은 SizedBox를 활용한 해결방법이다.
SizedBox를 활용하여 Scrollable Widget의 높이(너비)를 지정해주면, Listview가 제한된 크기를 가지게 되므로 문제를 해결 할 수 있다. 이 방법은 내부 아이템의 크기가 정해져 있는 경우 이 방법이 빠른 해결책이 될 수도 있다.
2. ShrinkWrap 사용
두번째로는 ShrinkWrap을 사용하는 방법이 있다.
shrinkWrap: true 설정을 하게되면, Listview는 자신의 높이(너비)를 강제로 “모든 아이템들의 크기의 합”으로 정의하게 된다.
예를 들어, 5개의 TextField가 Listview 안에 속해있는 상태에서 shrinkWrap: true 를 하게되면, Listview는 자신의 높이를 5개의 TextField 높이를 모두 합친 높이로 정의한다.
이처럼 Listview의 크기를 강제로 정의하면, Listview의 크기가 무한대로 확장되지 않으므로 오류가 발생하지 않게된다.
그러나, 이 방법은 추천하지 않는다.
첫번째로 비용적인 측면에서, shrinkWrap: true
설정 시 모든 아이템을 동시에 렌더링하기 때문이다.
Listview가 효율적으로 동작이 가능한 이유는, 화면에 보이는 부분만 렌더링하기 때문이다.
그러나 이 설정을 적용하는 순간 Listview의 높이는 전체 아이템의 높이가 되고, 모든 아이템이 한 화면에 나타나도록 렌더링하게 된다.
만약, Listview에 있는 아이템의 개수가 무한대로 늘어난다면 Flutter는 모든 아이템을 한꺼번에 렌더링하려하게 되므로 정상적인 동작이 불가능하게된다.
두번째로, 위의 예제와 같은 경우 Listview의 주요한 기능인 스크롤링 기능을 상실하게된다.
그 이유는 Colum안에 있는 Listview의 높이가 전체 아이템의 높이가 되기 때문인데, 이 경우 Listview의 범위 밖으로 벗어나는 아이템이 없기때문에 Listview는 스크롤링을 지원할 필요가 없어지는 것이다.
이 경우에는 Colum을 Listview로 대체하거나 SingleChildScrollView와 같은 위젯으로 한번 더 감싸서 해결할 수 있지만, 이 방법은 매우 비효율적이고 굳이 쓸 이유도 없다.
3. Expaned 위젯 사용
가장 추천하는 방법은 위처럼 Expanded 위젯을 사용하는 것이다.
Expanded 위젯은 하위 위젯의 크기를 상위 위젯이 제한하는 최대 크기까지 확장한다.
예를 들어, Container 위젯 안에 Expanded 위젯이 존재한다고 가정했을 때, Container 위젯에 어떠한 제약조건도 걸려있지 않다면(padding/margin 등), Expanded 위젯은 남는 화면 전체를 차지하게 된다.
이처럼 Expanded 위젯이 상위 위젯의 제약조건에 맞추어 자신의 최대 크기를 설정하게되면, 모든 문제가 해결된다.
위처럼 Expanded 위젯으로 내부의 Listview를 감싸면 Listview는 남는 화면 전체의 크기를 높이로 가지게되고, 해당 높이보다 큰(화면을 넘어가는) 아이템들에 대해 스크롤을 지원하게 되는 것이다.
마치며
이번에는 프로젝트 과정에서 발생한 오류에 대해 근본적인 원인부터 해결방법까지 다루어보았다.
사실 구글링을 통해서 문제를 해결하면 빠르고 간단하지만, 그것보다는 공식문서를 활용해 기본적인 원리를 파악하는게 장기적으로 훨씬 도움이 될 것이라 생각한다.
'Dart & Flutter' 카테고리의 다른 글
[Flutter] Login UI with Firebase Tutorial (0) | 2023.04.22 |
---|---|
[Dart 기초] Function (0) | 2023.04.05 |
[Error] Use context before initState() completed (0) | 2023.04.02 |
[Error] Could not find the correct Provider above this Widget (0) | 2023.03.23 |
[Flutter] Stateless Widgets (0) | 2023.03.21 |