[트러블슈팅] Flutter 앱 구현에 있어서 나타났던 트러블슈팅
문제 상황 1
Column 위젯을 ListView 로 변경
Column 에 추가되어 있던 Container 들의 개수가 많아져서 스크롤로 보여주기 위해 ListView 로 변경하려 한다. 그래서 Column 위젯을 냅다 ListView 로 바꿔주면 화면이 깨지면서 다음과 같은 오류가 나타나게 된다.


문제
The following assertion was thrown during performResize():
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.
If this widget is always nested in a scrollable widget there is no need to use a viewport because there will always be enough vertical space for the children. In this case, consider using a Column or Wrap instead. Otherwise, consider using a CustomScrollView to concatenate arbitrary slivers into a single scrollable.
RenderBox was not laid out: RenderFlex#64341 relayoutBoundary=up1 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
'package:flutter/src/rendering/box.dart':
Failed assertion: line 2164 pos 12: 'hasSize'
오류의 내용은 다음과 같다.
수직 viewports 는 높이가 지정되어 있지 않다. viewports 는 스크롤 방향으로 확장되어 컨테이너를 채운다. 이 경우 수직 viewports 에는 확장할 수 있는 무제한 수직 공간이 주어졌다. 이 상황은 일반적으로 스크롤 가능 위젯이 다른 스크롤 가능 위젯 안에 중첩되어 있을 때 발생한다.
최상위 위젯이 Column 인 것을 ListView 로 바꿔줄때는 상관이 없다.

변경하고자 하는 Column 이 상위 Column 에 감싸져 있을 때 ListView 의 높이가 제한되지 않아 무한하게 확장되면 부모 Column 을 침범하게 되어 위와 같은 오류가 나타난다.

해결
Column 의 height 가 무한으로 확장되는 것을 방지하기 위해서는 자식 ListView 에게 크기를 지정해주어야 한다. 따라서
ListView 를 Expanded 로 감싸주어 동적으로 공간을 할당해주면 위와 같은 오류는 나타나지 않는다.
Column( // 부모 Column
children: [
Expanded( // 추가
child: ListView( // 변경하고자 했던 부분(Column -> ListView)
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
child1,
child2,
child3,
...
문제 상황 2
Column 으로 나열되어 있는 위젯들을 세로 중앙으로 이동
왼쪽으로 치우쳐 있는 UI 를 예쁘게 중앙으로 배치하고 싶다.

위젯 구조
- Column
- Row
- Text(수서)
- Icon
- Text(부산)
- Row
- Container
- Text(선택됨)
- Container
- Text(선택안됨)
- ListView
- Container
- Container
- ...
시도했던 해결방안
Column 에서 위치를 지정하면 하위 위젯에 일일이 지정하지 않아도 될테니까? Column 만 죽어라 건들였던 거 같다.
1. 최상위 Column 에서 crossAxisAlignment.center 지정
2. Column 을 Center 로 감싸고 Column 내에서 mainAxisAlignment.center 지정
진짜 해결
하지만 나의 계획이 무너졌고... 이를 해결할 수 있었던 방법은 Column 하위 위젯인 Row 와 ListView 에 각각 mainAxisAlignment.center 값으로 지정하는 것이다.
시도했던 해결 방안들이 동작하지 않았던 이유는?

최상위의 Row 나 Column 은 크기를 지정하지 않을 경우 화면을 가득채운다. 정확히 말하면 Row 는 width 는 화면의 가로 길이와 동일하고 height는 자식 위젯의 사이즈에 따라 지정된다. 반면 Column 은 width 는 자식의 위젯 사이즈에 따라 달라지지만 height 는 화면의 세로 길이와 동일한 크기를 갖는다.
간단한 예시

// Column 예제
Container(
decoration: BoxDecoration(color: Colors.green),
child: Column(
children: [
Text('Parent Column', style: TextStyle(fontSize: 20))]),
)
// Row 예제
Container(
decoration: BoxDecoration(color: Colors.amber),
child: Row(
children: [
Text('Parent Row', style: TextStyle(fontSize: 20))]),
)
다음은 Row 와 Column 에 자식 위젯이 있을 경우 변화하는 height 과 width 길이의 예시이다. 첫번째 이미지의 Parent Column 은 자식 위젯인 Row 위젯의 넓이만큼 width 를 차지하게 됐다. 두번째 이미지의 Parent Row 는 자식 위젯인 Column 위젯의 높이만큼 height 을 차지하게 됐다.

첫번째 해결 방법은 Row 로 이미 Column width 부분을 다 차지했기 때문에 center 를 지정하더라도 움직이지 않았던 것이다.
두번째 해결 방법 마찬가지로 Column 이 화면을 이미 채웠기 때문에 세로 정렬이 불가능했다.
내가 원하는 것은 텍스트나 좌석 시트 등이 중앙으로 이동하는 것이므로 Column 의 mainAxisAlignment 를 변경하는 것이 아니라 Row 의 mainAxisAlignment 값을 center 로 맞춰주어야 한다.

문제 상황 3
Container - GestureDetector 구조에서 텍스트 외 컨테이너 부분을 클릭했을 때 동작하지 않는 이유
텍스트 부분이 아닌 한 라인을 눌렀을 때 동작했으면 하는데 텍스트 부분을 눌러야만 실행되는 문제가 있었다.

UI 의 위젯 구조
- Container
- GestureDetector
- Text
- Container
- GestureDetector
- Text
...
이유는?
If this widget has a child, it defers to that child for its sizing behavior.
GestureDetector 의 동작 방식은 child 에서 작동하기 때문에 Container 를 클릭했을 때 동작하길 원한다면 GestureDetector 의 child 를 Container 로 지정해야 한다.
해결
GestureDetector - Container - Text 위젯 구조를 가지도록 개선한다.
GestureDetector(
onTap: () {
// 동작
},
child: Container(
alignment: Alignment.centerLeft,
width: double.infinity,
height: 50,
child: Text(‘역 이름’),
...
)
참고: https://api.flutter.dev/flutter/widgets/GestureDetector-class.html
GestureDetector class - widgets library - Dart API
A widget that detects gestures. Attempts to recognize gestures that correspond to its non-null callbacks. If this widget has a child, it defers to that child for its sizing behavior. If it does not have a child, it grows to fit the parent instead. By defau
api.flutter.dev
문제 상황 4
다크 모드에서 container color 가 black 색상으로 나오는 문제

앱의 다크 모드 지원을 위해서 dark ThemeData 에서 cardColor 값을 light 와 dark 에서 각각 white, black54 의 색상을 가지도록 지정을 해줬다. 그리고 위의 Container 에서 decoration 을 cardColor 를 가져오게끔 코드를 작성했다.
final lightTheme = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.purple,
brightness: Brightness.light,
),
cardColor: Colors.white, // HomePage 의 출발도착 선택 박스 색상 지정
);
final darkTheme = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.purple,
brightness: Brightness.dark,
),
cardColor: Colors.black54,
);
Container(
height: 200,
...
decoration: BoxDecoration(
color: Theme.of(context).cardColor, // ThemeData 에서 지정한 cardColor 값 가져오기
...
),
)
하지만 dark 모드로 변경하면 black54 가 아닌 진한 검정색이 나오는 문제가 발생한다.
알고보니 이유는
black12, black26, black38 등 색상 뒤에 숫자가 적혀있는 color 들은 투명도가 있는 색상이므로 scaffoldBackgroundColor 의 색상에 의해(불투명에 black 에 가까운 색이므로) 그냥 검정색으로 보이는 것이었다.
참고 https://api.flutter.dev/flutter/material/Colors/black12-constant.html
black12 constant - Colors class - material library - Dart API
Color const black12 Black with 12% opacity. Used for the background of disabled raised buttons in light themes. See also: Implementation static const Color black12 = Color(0x1F000000);
api.flutter.dev
해결
dark 모드에서 cardColor 가 투명도 255 를 가진 색상으로 지정을 해준다.
final darkTheme = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.purple,
brightness: Brightness.dark,
),
cardColor:
const Color.fromARGB(255, 80, 80, 80), // HomePage 의 출발도착 선택 박스 색상 지정
);
이번 앱 만들면서 느낀점
실습 한 번 진행하고 최대한 강의를 안 보고 실습 코드보고 이해하면서 하나하나 진행해나갔다. 완전히 내 것으로 만들기는 어렵겠지만 복붙을 최대한 지양하고, 외우고 여러 번 반복해서 코드를 작성하려고 노력했다. 트러블슈팅에 쓸 내용들을 쭉 확인해봤는데 UI 배치나 위젯 특성에 대한 내용이 대부분인 것을 보고 어느 부분에 취약한지 알았다. 트러블슈팅 2번을 기점으로 대략적인 감이 잡힌 거 같다는 생각이 들었다. Row 나 Column 에 대한 기본적인 내용을 많이 찾아봤다. 이번에는 정적인 위젯을 많이 사용한 거 같은데 다음 프로젝트에서는 반응형 위젯을 사용하는 것을 목표로 해봐야겠다.