본문 바로가기
ERROR

[Flutter error] RenderBox was not laid out: RenderViewport#d4bc2 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE'package:flutter/src/rendering/box.dart':box.dart:1Failed assertion: line 1966 pos 12: 'hasSize'

by Nuridal_class 2023. 10. 17.
728x90
728x90

 ListView error

제목에서 보이는 오류는 대부분 ListView나 GridView를 사용할때 보이는 오류이다.
또는 아래 사진과 같이 Horizontal viewport was given unbounded height. 
이런 오류를 심심치 않게 볼수 있는데 해결방법을 알아보도록 합시다.

Horizontal viewport was given unbounded height.

 

예시코드

아래와 같은 코드일때 주로 오류가 발생하니 한번 확인해봅시다.
아래와 같이 boby > Column > ListView.builder를 사용해서 코드를 작성했습니다.
언뜻 보기에는 아무런 문제없다고 생각하지만 제목과 같은 오류를 딱 맞딱뜨리게 될것입니다.
import 'package:flutter/material.dart';

class ListViewSetState extends StatefulWidget {
  const ListViewSetState({super.key});

  @override
  State<ListViewSetState> createState() => _ListViewSetState();
}

class _ListViewSetState extends State<ListViewSetState> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            backgroundColor: Theme.of(context).colorScheme.inversePrimary,
            title: const Text('Nuridal Class: Listview',
                style: TextStyle(fontSize: 20))),
        body: Center(
            child: Column(
                children: <Widget>[
              ListView.builder(
                scrollDirection: Axis.horizontal,
                padding: const EdgeInsets.all(8),
                itemCount: 10,
                itemBuilder: (BuildContext context, int index) {
                  return SizedBox(
                    width: 100,
                    height: 100,
                    child: Center(child: Text('Item $index')),
                  );
                },
              ),
              ElevatedButton(
                onPressed: () {
                  Navigator.pop(context);
                },
                child: const Text('back_page'),
              )
            ])));
  }
}

 

 왜 이런 오류가 뜨는건가?

이유는 ListView는 부모 widget의 높이에 따라 높이를 맞추게 되는데 이때 Column의 높이는 무한이기 때문에 
ListView의 높이를 따로 설정해주지 않으면 에러가 발생하는 것입니다.
그 반대로 말하면 부모 위젯이 높이가 정해져 있는 위젯이라면 오류가 발생하지 않는다는 뜻도 됩니다.

 

 해결방법 👉 1. shrinkwrap : true

첫번째 해결 방법으로는 ListView 안에 shrinkwrap : true 값을 주는 것입니다.
        body: Center(
        //📲 Column 자체를 스크롤하게끔 하는 코드 (SingleChildScrollView)
            child: SingleChildScrollView(
          child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                ListView.builder(
                  shrinkWrap: true,
                  //📲 ListView 자체의 스크롤을 막기 위한 코드
                  physics: const NeverScrollableScrollPhysics(),
                  padding: const EdgeInsets.all(8),
                  itemCount: 10,
                  itemBuilder: (BuildContext context, int index) {
                    return SizedBox(
                      width: 100,
                      height: 100,
                      child: Center(child: Text('Item $index')),
                    );
                  },
                ),
                ElevatedButton(
                  onPressed: () {
                    Navigator.pop(context);
                  },
                  child: const Text('back_page'),
                )
              ]))
그러나 shrinkwrap : true를 하면 child 크기만큼만 높이를 정해주기 때문에
보여줄 데이터가 화면에 벗어날 정도로 많다면 오버플로우 오류가 발생하기가 쉽다는 단점이 있습니다.
이때 해결 방법이 physics : const NeverScrollableScrollPhysics()SingleChildScrollView를 사용합니다.

shrinkwrap로 크기를 정해줬기 때문에 오버플로우가 생깁니다. 
👉 상단 부모에게 스크롤이 가능하도록 SingleChildScrollView를 선언해줘서 오버플로우를 막아줍니다.
👉 그러나 Single과 ListView의 스크롤이 겹치기 때문에 스크롤이 정상적으로 되지 않습니다.
👉 NeverScrollableScrollphysics를 선언해서 ListView의 스크롤을 막음으로 해결합니다.

아래는 해당 결과값을 보여줍니다.

ListView shrinkwrap

 

 2. Expanded

또 다른 방법은 Expanded를 사용하는 것입니다.
        body: Center(
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                //📲 ListView 상단에 선언해줍니다
              Expanded(
                child: ListView.builder(
                  padding: const EdgeInsets.all(8),
                  itemCount: 10,
                  itemBuilder: (BuildContext context, int index) {
                    return SizedBox(
                      width: 100,
                      height: 100,
                      child: Center(child: Text('Item $index')),
                    );
                  },
                ),
              ),
              ElevatedButton(
                onPressed: () {
                  Navigator.pop(context);
                },
                child: const Text('back_page'),
              )
Expanded를 선언해줌으로 Column의 위젯이 사용가능한 공간을 확장해주기 때문에 문제없이 사용이 가능하다.
필자는 이 방법을 가장 선호하기도 하며 권장합니다. 왜냐면! 간단하기 때문입니다 ㅎㅎ

아래는 해당 결과값을 보여줍니다.

 

 3. SizedBox

마지막 방법은 SizedBox를 사용하는 것입니다.
body: Center(
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                //📲 SizedBox로 높이를 지정해줍니다.
              SizedBox(
                height: 300,
                child: ListView.builder(
                  padding: const EdgeInsets.all(8),
                  itemCount: 10,
                  itemBuilder: (BuildContext context, int index) {
                    return SizedBox(
                      width: 100,
                      height: 100,
                      child: Center(child: Text('Item $index')),
                    );
                  },
                ),
              ),
              ElevatedButton(
                onPressed: () {
                  Navigator.pop(context);
                },
                child: const Text('back_page'),
              )
사용자의 입맛에 맞게 설정하는 방법이긴 합니다만 앱을 개발할때 반응형으로 개발하지 않으면
핸드폰 크기마다 맞게 바꿔주게 되는 불상사가 생길수도 있습니다.
정말 필요할때만 사용하시를 권장드리는 방법입니다.

아래는 해당 결과값을 보여줍니다.


 4. shrinkwrap / Expanded / SizedBox 차이점

3가지 해결방법에 대해서 알아보았는데 이때 각각의 차이점들이 보일 것입니다.
shrinkwrap = column위에 single..스크롤을 사용했기 때문에 버튼도 맨아래까지 스크롤을 내려야 보입니다.
expanded = ListView에 대해서만 확장한 것이기 때문에 버튼이 화면에 보입니다.
sizedbox = ListView에 대해서만 크기를 지정해줬기 때문에 버튼이 화면에 보입니다.
👉 위에 결과값을 봤듯이 back_page 버튼이 어디에 보이는지에 대한 차이점이 있었습니다.

 

ListView나 GridView를 사용할때 가장 흔히 보이는 오류와 해결방법에 대해서 알아보았습니다.
오류와 씨름할때 도움이 되기를 바라면서!!
코딩이 쉬워지는 그날까지!!

728x90
300x250