
RenderFlex overflowed 에러 해결: 노란 줄무늬의 악몽
Flutter 개발자라면 피할 수 없는 노란색/검은색 줄무늬 에러. 단순히 Expanded로 감싸는 게 답이 아닐 수 있습니다. 플러터의 제약 조건(Constraints) 시스템을 완벽하게 이해해봤다.

Flutter 개발자라면 피할 수 없는 노란색/검은색 줄무늬 에러. 단순히 Expanded로 감싸는 게 답이 아닐 수 있습니다. 플러터의 제약 조건(Constraints) 시스템을 완벽하게 이해해봤다.
로그인 화면을 만들었는데 키보드가 올라오니 노란 줄무늬 에러가 뜹니다. resizeToAvoidBottomInset부터 스크롤 뷰, 그리고 채팅 앱을 위한 reverse 팁까지, 키보드 대응의 모든 것을 정리해봤습니다.

안드로이드는 오는데 iOS는 조용합니다. 혹은 앱이 켜져 있을 때만 옵니다. Background/Terminated 상태 처리, APNs 인증서, 그리고 Notification Channel 설정까지 완벽하게 해결합니다.

분명히 클래스를 적었는데 화면은 그대로다? 개발자 도구엔 클래스가 있는데 스타일이 없다? Tailwind 실종 사건 수사 일지.

안드로이드는 Xcode보다 낫다고요? Gradle 지옥에 빠져보면 그 말이 쏙 들어갈 겁니다. minSdkVersion 충돌, Multidex 에러, Namespace 변경(Gradle 8.0), JDK 버전 문제, 그리고 의존성 트리 분석까지 완벽하게 해결해 봅니다.

Flutter를 처음 시작하고 Row나 Column을 쓰다 보면, 어느 순간 화면 오른쪽에 끔찍한 노란색과 검은색의 사선 줄무늬(Caution Tape)가 나타납니다.
마치 "여기 출입 금지 구역임"이라고 말하는 것 같죠.
그리고 콘솔에는 빨간 글씨가 폭포수처럼 쏟아집니다.
════════ Exception caught by rendering library ═════════════════════════════════
RunA typical RenderFlex overflowed by 52 pixels on the right.
The relevant error-causing widget was:
Row ...
════════════════════════════════════════════════════════════════════════════════
초보 시절 저는 이 에러를 보고 그냥 "아, 텍스트가 너무 긴가 보네?" 하고 Container에 width를 300으로 고정해버렸습니다.
혹은 구글링해서 나온 대로 무작정 Expanded로 감싸버렸죠.
해결된 줄 알았습니다. 다른 기기(아이폰 SE)에서 확인하기 전까지는요.
이 에러는 단순히 공간이 부족하다는 뜻이 아닙니다. "당신은 지금 플러터의 레이아웃 법칙을 위배했다"는 신호입니다.
Flutter의 레이아웃 시스템을 이해하려면 "Constraints(제약 조건)는 부모가 주고, Size(크기)는 자식이 결정한다"는 대원칙을 알아야 합니다.
RenderFlex overflowed는 주로 Row나 Column (Flex 위젯들)에서 발생합니다.
상황을 비유해볼까요?
여기서 중요한 건, Flutter의 Row는 기본적으로 "자식들이 달라는 대로 다 주려고 노력한다"는 점입니다. (MainAxisSize.max)
자식이 500px을 달라고 하면, "야 그거 화면 뚫고 나가는데?"라고 말리지 않고, 그냥 500px을 줘버립니다. 그래서 화면 밖으로 튀어나가고 경고 줄무늬가 뜨는 겁니다.
가장 정석적인 해결책입니다. 자식에게 "야, 너 고집부리지 말고 남은 공간만큼만 먹어"라고 명령하는 것입니다.
Row(
children: [
Text("짧은 제목"),
// ❌ 에러 발생: 텍스트가 길어지면 화면 뚫고 나감
Text("엄청나게 긴 설명 텍스트........"),
],
)
이걸 이렇게 고칩니다.
Row(
children: [
Text("짧은 제목"),
// ✅ 해결: 남은 공간(n분의 1)만 차지하고, 글자는 줄바꿈됨
Expanded(
child: Text("엄청나게 긴 설명 텍스트........"),
),
],
)
Expanded는 "부모(Row)가 가진 남은 공간을 전부 채워라"는 뜻입니다.
만약 Expanded가 두 개라면? 사이 좋게 50:50으로 나눠 가집니다. 비율을 다르게 하고 싶다면 flex 속성을 쓰면 됩니다.
Row(
children: [
Expanded(flex: 1, child: Container(color: Colors.red)), // 1/3
Expanded(flex: 2, child: Container(color: Colors.blue)), // 2/3
],
)
Expanded는 Row, Column, Flex의 직계 자식(Direct Child)일 때만 작동합니다.
아래처럼 Container 등으로 한 번 감싸면 효과가 사라지고 에러가 납니다.
// ❌ 에러: Expanded는 Row의 바로 아래에 있어야 함
Row(
children: [
Container(
child: Expanded( // 엉뚱한 곳에 있음
child: Text("..."),
),
),
],
)
애초에 콘텐츠가 화면보다 큰 게 문제라면, 화면을 늘리면 되지 않을까요? 바로 스크롤입니다.
// ✅ 해결: 넘치면 스크롤 가능
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [ ... ],
),
)
하지만 Column 전체를 SingleChildScrollView로 감쌀 때는 조심해야 합니다. 성능 이슈 때문입니다.
SingleChildScrollView는 자식들을 한 번에 다 그립니다. 자식이 100개라면 100개를 다 메모리에 올립니다.
자식이 많다면 무조건 ListView나 CustomScrollView를 써야 합니다. (Lazy Loading)
ListView를 쓰다 보면 또 다른 에러(Vertical viewport was given unbounded height)를 만나게 되는데, 이때 구글링을 하면 shrinkWrap: true를 쓰라는 답변이 많이 나옵니다.
Column(
children: [
Text("헤더"),
ListView(
shrinkWrap: true, // ⚠️ 위험한 해결책
physics: NeverScrollableScrollPhysics(), // 스크롤 안 되게
children: [ ... ],
),
],
)
이 코드는 "ListView야, 너 가능한 한 작게 줄어들어라"라고 하는 건데, 이렇게 하면 ListView의 장점인 Lazy Loading(화면에 보이는 것만 그리기)이 사라집니다.
리스트 아이템이 1,000개면 1,000개를 한 번에 다 계산해서 높이를 측정해야 하기 때문입니다. 앱이 버벅거릴 수밖에 없습니다.
이럴 땐 CustomScrollView와 Slivers를 쓰는 게 "진짜(Pro)" 해결책입니다.
고급 Flutter 개발자로 가기 위한 관문, Slivers입니다.
Sliver는 "조각"이라는 뜻으로, 스크롤 가능한 영역을 조각조각 내어서 효율적으로 관리하는 시스템입니다.
위의 shrinkWrap 문제를 해결하려면 이렇게 짭니다.
CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Text("헤더"), // 일반 위젯은 Adapter로 감쌈
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text("아이템 $index")),
childCount: 1000,
),
),
],
)
이렇게 하면, 헤더와 리스트가 하나의 스크롤 뷰 안에 공존하면서도, 리스트는 화면에 보이는 것만 그리는(Lazy Loading) 성능을 유지할 수 있습니다. 복잡한 스크롤 UI(앱바가 줄어드는 효과 등)를 구현할 때 필수입니다.
텍스트 입력창(TextField)이 있는 화면에서 키보드를 띄우면, 갑자기 바닥에서 노란 줄무늬가 올라옵니다.
키보드가 화면의 절반을 가려버리면서, 원래 있던 UI들이 찌그러져서 오버플로우가 난 겁니다.
resizeToAvoidBottomInsetScaffold 속성으로 제어합니다.
Scaffold(
resizeToAvoidBottomInset: false, // 키보드가 올라와도 화면을 줄이지 않음 (가려짐)
body: ...
)
이러면 에러는 안 나지만, 입력창이 키보드에 가려져서 안 보일 수 있습니다.
가장 좋은 방법은 전체 화면을 SingleChildScrollView로 감싸는 것입니다. 그러면 키보드가 올라와서 화면이 좁아져도, 사용자가 스크롤해서 내용을 볼 수 있습니다.
Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
// ... 긴 내용
TextField(),
],
),
),
)
Row나 Column, Stack으로도 도저히 해결이 안 되는 복잡한 디자인 요구사항이 있다면?
예를 들어, "자식 A의 위치는 자식 B의 너비의 절반만큼 오른쪽으로 이동해야 한다" 같은 경우입니다.
이때는 CustomMultiChildLayout을 써야 합니다.
개발자가 직접 Delegate를 구현해서, 자식 하나하나의 크기와 위치(Offset)를 수학적으로 계산해서 배치해주는 방식입니다.
난이도가 높지만, RenderFlex의 한계를 넘어설 수 있는 유일한 방법입니다.
눈대중으로 때려 맞히지 마세요. Flutter DevTools의 "Widget Inspector"를 켜면 됩니다.
"Layout Explorer" 탭을 누르면, 현재 위젯이 부모로부터 어떤 제약(Constraint)을 받고 있는지, 자식에게 어떤 크기를 주고 있는지 시각적으로 보여줍니다.
여기서 Flex 값을 실시간으로 조절해보며 오버플로우를 해결할 수도 있습니다.
RenderFlex overflowed는 Flutter가 여러분에게 보내는 선물입니다. "너 UI 설계를 잘못했어"라고 친절하게 알려준 겁니다.
Row/Column 안에서 크기가 가변적인 녀석은 Expanded로 감싸라.SingleChildScrollView나 ListView를 써라.shrinkWrap을 남용말라: CustomScrollView + Slivers가 정석이다.Container(width: 300) 같은 매직 넘버를 피하라: 모든 기기에서 깨진다. 비율과 제약 조건(Constraints)으로 디자인하라.줄무늬 테이프를 두려워하지 마세요. 그 너머에 완벽한 반응형 UI가 기다리고 있습니다.