Skip to content

Flutter 오류 대응법 가이드

Flutter 앱을 개발하면서 다양한 오류에 직면할 수 있습니다. 이 문서에서는 자주 발생하는 Flutter 오류들과 그 해결 방법을 소개합니다.

Flutter SDK를 설치한 후에는 항상 flutter doctor 명령어를 실행하여 개발 환경을 확인하는 것이 좋습니다.

오류 메시지원인해결 방법
Flutter requires Android SDKAndroid SDK가 설치되지 않았거나 경로가 설정되지 않음Android Studio를 설치하거나, Android SDK 경로를 Flutter 환경 변수에 추가
Android licenses not acceptedAndroid 라이선스 동의가 필요함flutter doctor --android-licenses 실행 후 동의
Xcode not installediOS 개발을 위한 Xcode가 설치되지 않음 (macOS만 해당)App Store에서 Xcode 설치
CocoaPods not installediOS 종속성 관리 도구가 설치되지 않음sudo gem install cocoapods 명령어로 설치
Because every version of flutter_package from sdk depends on intl ^0.17.0 and app depends on intl ^0.18.0, flutter_package from sdk is forbidden.

원인: 패키지 간 의존성 충돌 해결 방법: 패키지 버전을 호환되는 범위로 조정하고 flutter pub upgrade --major-versions 실행

Gradle task assembleDebug failed with exit code 1

원인: 다양한 Gradle 구성 문제 발생 가능 해결 방법:

  1. 안드로이드 폴더에서 ./gradlew clean 실행
  2. flutter clean 실행 후 다시 빌드
  3. Gradle 버전 확인 및 업데이트
[!] CocoaPods could not find compatible versions for pod "Firebase/Core"

원인: CocoaPods 의존성 충돌 해결 방법:

  1. iOS 폴더에서 pod repo update 실행
  2. pod install --repo-update 실행
  3. Podfile.lock 삭제 후 pod install 다시 실행
No provisioning profile matches the specified entitlements

원인: iOS 앱 서명 설정 문제 해결 방법:

  1. Xcode를 열고 팀 설정 확인
  2. 프로비저닝 프로필 업데이트
  3. 앱 ID와 번들 ID가 일치하는지 확인
setState() called after dispose(): _MyWidgetState#a7c89(lifecycle state: defunct, not mounted)

원인: 위젯이 제거된 후 setState 호출 해결 방법:

// 수정 전
void fetchData() async {
final data = await apiService.getData();
setState(() {
this.data = data;
});
}
// 수정 후
void fetchData() async {
final data = await apiService.getData();
if (mounted) {
setState(() {
this.data = data;
});
}
}
A RenderFlex overflowed by 20 pixels on the bottom

원인: 자식 위젯이 부모 위젯의 제약 조건을 초과 해결 방법:

  1. Expanded 또는 Flexible 위젯 사용
  2. SingleChildScrollView로 스크롤 가능하게 만들기
  3. ConstrainedBox를 사용하여 최대 크기 제한
// 오류 발생 코드
Column(
children: [
LargeWidget(),
AnotherLargeWidget(),
],
)
// 해결 방법 (스크롤 적용)
SingleChildScrollView(
child: Column(
children: [
LargeWidget(),
AnotherLargeWidget(),
],
),
)
// 또는 Expanded 사용 (부모가 Row/Column일 경우)
Column(
children: [
Expanded(child: LargeWidget()),
AnotherWidget(),
],
)
Error: Could not find the correct Provider<MyModel> above this Consumer Widget

원인: Provider가 위젯 트리의 상위에 없음 해결 방법: 적절한 위치에 Provider 배치

// 수정 전 (문제가 되는 구조)
Widget build(BuildContext context) {
return Consumer<MyModel>(
builder: (context, model, child) {
return Text(model.data);
},
);
}
// 수정 후 (올바른 구조)
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => MyModel(),
child: Consumer<MyModel>(
builder: (context, model, child) {
return Text(model.data);
},
),
);
}
ProviderNotFoundException: No provider found for providerHash:xxx

원인: ProviderScope 범위 밖에서 provider에 접근 시도 해결 방법: 앱 루트에 ProviderScope 추가

void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
Access to XMLHttpRequest has been blocked by CORS policy

원인: 웹 버전에서 CORS 정책 위반 해결 방법:

  1. 서버 측에서 적절한 CORS 헤더 설정
  2. 개발 시에는 Chrome을 --disable-web-security 옵션으로 실행
  3. 프록시 서버 사용
HandshakeException: Handshake error in client

원인: SSL 인증서 문제 해결 방법:

// 주의: 프로덕션 앱에서는 사용하지 않는 것이 좋습니다
HttpClient client = HttpClient()
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
type 'Future<dynamic>' is not a subtype of type 'Future<List<String>>'

원인: Future의 타입 불일치 해결 방법: 명시적 타입 지정

// 수정 전
Future fetchData() async {
// ...
return data;
}
// 수정 후
Future<List<String>> fetchData() async {
// ...
return data;
}
Bad state: Stream has already been listened to

원인: 단일 구독 스트림에 여러 번 구독 시도 해결 방법: Stream.asBroadcastStream() 사용 또는 StreamController에서 broadcast: true 설정

Execution failed for task ':app:mergeDebugResources'. > java.lang.OutOfMemoryError

원인: 메서드 수가 DEX 제한을 초과 해결 방법: android/app/build.gradle에 멀티덱스 활성화

android {
defaultConfig {
multiDexEnabled true
}
}
dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
}
PlatformException: Permission denied

원인: 필요한 권한이 설정되지 않음 해결 방법: AndroidManifest.xml에 필요한 권한 추가 및 런타임 권한 요청 구현

<manifest>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<!-- 필요한 권한 추가 -->
</manifest>
This app has crashed because it attempted to access privacy-sensitive data without a usage description.

원인: 필요한 권한 설명이 Info.plist에 없음 해결 방법: ios/Runner/Info.plist에 관련 설명 추가

<key>NSCameraUsageDescription</key>
<string>이 앱은 프로필 사진 등록을 위해 카메라에 접근합니다.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>이 앱은 사진 업로드를 위해 갤러리에 접근합니다.</string>
The iOS deployment target is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.

원인: 지원되지 않는 iOS 최소 버전 설정 해결 방법: ios/Podfile 및 Xcode 프로젝트 설정에서 최소 버전 업데이트

# Podfile
platform :ios, '12.0'
TypeError: Cannot read property 'X' of undefined

원인: 웹용 플러그인이 올바르게 초기화되지 않음 해결 방법: 플랫폼 조건부 코드 사용

import 'package:flutter/foundation.dart' show kIsWeb;
if (kIsWeb) {
// 웹 전용 코드
} else {
// 모바일 전용 코드
}
Failed to load resource: net::ERR_BLOCKED_BY_CLIENT

원인: 광고 차단기가 리소스 로딩 차단 해결 방법: 리소스 URL 패턴 변경 또는 필수 리소스임을 사용자에게 알림

MissingPluginException(No implementation found for method X on channel Y)

원인: 플러그인 초기화 문제 또는 플랫폼 지원 부재 해결 방법:

  1. 앱 재시작
  2. flutter clean 실행 후 다시 빌드
  3. 플러그인 호환성 확인 및 조건부 코드 작성
Undefined name 'X'. (The name X isn't a type and can't be used in a declaration)

원인: API 변경으로 인한 코드 호환성 문제 해결 방법:

  1. 패키지 버전 확인 및 호환되는 버전 사용
  2. 최신 API에 맞게 코드 업데이트
  3. 코드 마이그레이션 가이드 참조
A Timer still exists even after the widget tree was disposed.

원인: dispose 메서드에서 타이머, 컨트롤러 등을 해제하지 않음 해결 방법:

class _MyWidgetState extends State<MyWidget> {
AnimationController _controller;
Timer _timer;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_timer = Timer.periodic(Duration(seconds: 1), (_) => update());
}
@override
void dispose() {
_controller.dispose(); // 컨트롤러 해제
_timer.cancel(); // 타이머 취소
super.dispose();
}
}
Out of memory: Bytes allocation failed

원인: 과도한 메모리 사용 해결 방법:

  1. 이미지 캐싱 라이브러리 사용 (cached_network_image)
  2. 적절한 크기의 이미지 로드 (ResizeImage 또는 서버 측 리사이징)
  3. 메모리에 저장하는 데이터 제한
The parameter 'onPressed' is required

원인: 필수 매개변수 누락 해결 방법: 코드 분석 도구가 지적한 문제 해결

원인: 불필요한 위젯 리빌드로 인한 성능 저하 해결 방법:

  1. const 생성자 사용
  2. 상태 변경을 더 작은 위젯으로 분리
  3. RepaintBoundary를 사용하여 리페인트 영역 제한
// 개선 전
ListView.builder(
itemBuilder: (context, index) {
return MyListItem(data: items[index]);
}
)
// 개선 후
ListView.builder(
itemBuilder: (context, index) {
return RepaintBoundary(
child: const MyListItem(data: items[index]),
);
}
)

Flutter DevTools는 Flutter 앱 개발 시 강력한 디버깅 도구입니다. 다음과 같은 기능을 제공합니다:

  1. Inspector: 위젯 트리 분석 및 레이아웃 디버깅
  2. Performance: 프레임 드롭 분석
  3. Memory: 메모리 사용량 및 누수 확인
  4. Network: 네트워크 요청 모니터링
  5. Logging: 로그 확인 및 필터링

효과적인 로깅은 문제를 빠르게 파악하는 데 도움이 됩니다:

  1. 로깅 레벨 구분:

    import 'package:logger/logger.dart';
    final logger = Logger(
    printer: PrettyPrinter(),
    );
    // 다양한 로깅 레벨 사용
    logger.v("Verbose log");
    logger.d("Debug log");
    logger.i("Info log");
    logger.w("Warning log");
    logger.e("Error log");
  2. 예외 처리:

    try {
    // 위험한 작업
    } catch (e, stackTrace) {
    logger.e("오류 발생", error: e, stackTrace: stackTrace);
    }

Flutter 앱 개발 중 발생하는 대부분의 오류는 체계적인 접근 방식으로 해결할 수 있습니다. 문제를 정확히 이해하고, 검색 엔진이나 Stack Overflow 같은 자료를 활용하며, 필요하다면 Flutter 이슈 트래커나 커뮤니티에 도움을 요청하세요.

오류 메시지를 무시하지 말고, 로그를 주의 깊게 읽고 분석하는 습관을 들이면 문제 해결 능력이 크게 향상됩니다. 또한 정기적으로 Flutter와 종속성을 최신 상태로 유지하는 것도 많은 문제를 예방할 수 있는 좋은 방법입니다.