Flutter 개발 시 자주 발생하는 실수 및 해결 방법

1. Widget의 부모 컨테이너 설정 오류

모든 위젯은 Container로 감싸는 것이 좋습니다. 특히 Row 또는 Column 내부에 TextField를 직접 넣는 경우, 반드시 Expanded으로 감싸야 합니다.

Row(
  children: [
    Expanded(
      child: TextField(
        decoration: InputDecoration(
          enabledBorder: UnderlineInputBorder(
            borderSide: BorderSide(color: Colors.orange),
          ),
          focusedBorder: UnderlineInputBorder(
            borderSide: BorderSide(color: Colors.red),
          ),
        ),
      ),
    ),
  ],
)

2. ListView 내부에서 크기 조절 문제

ListViewColumn 안에 있을 때는 shrinkWrap: true를 반드시 설정해야 합니다. 이 값이 없으면 리스트 뷰가 화면 전체 높이를 차지하므로 레이아웃이 깨집니다.

Column(
  children: [
    Text("상단 텍스트"),
    Expanded(
      child: ListView.builder(
        shrinkWrap: true,
        itemCount: 10,
        itemBuilder: (context, index) {
          return ListTile(title: Text("항목 $index"));
        },
      ),
    ),
  ],
)

3. 화면 리사이징 및 디바이스 대응

다양한 해상도와 화면 크기에 대응하기 위해 ScreenUtil이나 MediaQuery를 활용하세요.

  • 화면 하단 여백 계산: MediaQuery.of(context).viewInsets.bottom
  • 상단 상태바 높이: MediaQuery.of(context).padding.top

4. 이미지 로딩 및 예외 처리

네트워크 이미지 로드 실패 시, 로컬 이미지를 대체하여 표시할 수 있습니다.

Image.network(
  'https://example.com/image.jpg',
  height: double.infinity,
  width: double.infinity,
  fit: BoxFit.cover,
  errorBuilder: (context, error, stackTrace) => Image.asset(
    'assets/images/placeholder.png',
    height: double.infinity,
    width: double.infinity,
    fit: BoxFit.cover,
  ),
)

5. State 관리 및 비동기 처리

setState()는 현재 위젯 트리를 다시 빌드합니다. 비동기 작업 후 결과를 사용할 땐 FutureBuilder를 사용하고, 복잡한 위젯은 별도 클래스로 분리하세요.

FutureBuilder<Response>(
  future: fetchData(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.done) {
      return DetailWidget(data: snapshot.data!.data);
    }
    return CircularProgressIndicator();
  },
)

6. null-safe 연산자 활용

???.는 필수적인 연산자입니다.

  • a ?? b: a가 null이면 b 반환, 아니면 a 반환
  • a?.b: a가 null이면 null 반환, 아니면 a.b 호출

7. RichText로 다양한 스타일 텍스트 표현

여러 스타일을 결합해 출력할 때는 RichTextTextSpan을 사용하세요.

RichText(
  text: TextSpan(
    children: [
      TextSpan(
        text: "전문가 소개: ",
        style: TextStyle(fontSize: 18, color: Colors.blue),
      ),
      TextSpan(
        text: "10년 이상 경력, 전문성 높은 진료 제공",
        style: TextStyle(fontSize: 16, color: Colors.black),
      ),
    ],
  ),
)

8. 클립 및 그림자 충돌 문제

ClipRRect를 사용할 경우, BoxShadow가 제대로 작동하지 않을 수 있습니다. 이는 레이아웃 순서나 컨테이너의 배경 설정에 영향을 받을 수 있으므로, Container 내부의 decorationclipBehavior를 주의 깊게 설정하세요.

9. Flexible vs Expanded 차이

Expanded은 기본적으로 fit: FlexFit.tight이며, 가능한 한 공간을 채웁니다. 최대 높이 제한을 원한다면 Flexible로 변경하고 fit: FlexFit.loose를 지정하세요.

Flexible(
  fit: FlexFit.loose,
  flex: 1,
  child: Container(
    height: 100, // 최대 높이 제약
    color: Colors.grey[200],
  ),
)

10. 데이터 전송 시 형식 변환 주의

Flutter에서 List 또는 Map을 서버로 전송할 때는 직렬화가 필요합니다.

  • ListtoString() 또는 jsonEncode()
  • Mapjson.encode(mapData)
final data = {
  'images': json.encode(signImages), // Map을 문자열로 변환
};

11. Navigation UI 제어

상태바와 하단 바를 숨기거나 보이게 할 수 있습니다.

SystemChrome.setEnabledSystemUIMode(
  SystemUiMode.manual,
  overlays: [SystemUiOverlay.bottom], // 상태바만 숨기고 하단 바 유지
);

SystemChrome.setSystemUIOverlayStyle(
  SystemUiOverlayStyle(statusBarColor: Colors.transparent),
);

12. 복잡한 비동기 처리: compute 사용

메인 스레드에서 무거운 작업을 수행하면 레이아웃이 멈춥니다. compute()를 통해 새로운 isolate를 생성하여 처리하세요.

final result = await compute(longRunningTask, input);

태그: Flutter widget async FutureBuilder null safety

7월 19일 22:14에 게시됨