에러는 버그가 아니라 신호다
Flutter를 배우다 보면 이상한 순간을 반드시 만납니다. 방금 전까지 잘 실행되던 앱이 갑자기 빌드되지 않고, Android Studio를 업데이트했을 뿐인데 Gradle이 화를 내며, iOS 쪽에서는 CocoaPods가 낯선 영어 문장을 쏟아냅니다. 처음에는 마치 앱 개발 세계가 나를 거부하는 것처럼 느껴지지만, 대부분의 에러는 무작위로 발생하지 않습니다. 원인이 있고, 원인을 찾는 순서가 있습니다.
이 장은 새로운 개념을 배우는 장이라기보다, 개발자가 실제로 가장 많이 시간을 쓰는 '문제 해결'에 대한 장입니다. 앱 개발은 코드를 쓰는 일만큼이나 환경을 읽는 일입니다. 특히 Flutter는 Dart 코드만으로 앱을 만드는 것처럼 보이지만, 실제 빌드 과정에서는 Android의 Gradle, iOS의 Xcode와 CocoaPods, Dart 패키지 관리자, 에뮬레이터, 실제 기기, Firebase 설정까지 여러 도구가 함께 움직입니다.
그러니 에러가 났을 때 가장 먼저 할 일은 당황하는 것이 아니라 '어느 층에서 난 에러인지' 나누는 것입니다.
Flutter SDK와 IDE 설정 문제
Android SDK, Gradle, JDK 문제
Xcode, CocoaPods, Signing 문제
Dart 문법과 타입 문제
pub 패키지 의존성 문제
위젯 레이아웃과 런타임 상태 문제
에뮬레이터, 실제 기기, 권한 문제
이렇게 분류하면 혼란이 줄어듭니다. 모든 에러를 한 덩어리로 보면 거대한 괴물 같지만, 층을 나누면 작은 체크리스트가 됩니다.

가장 먼저 실행할 명령어: flutter doctor
Flutter 환경 문제를 만났다면 거의 항상 다음 명령어부터 시작합니다.
flutter doctorflutter doctor는 Flutter 개발 환경을 건강검진하듯 확인합니다. Android toolchain, Xcode, Chrome, 연결된 디바이스, Android Studio나 VS Code 같은 IDE 설정 상태를 한 번에 보여줍니다. Flutter 공식 Android 설정 문서에서도 Android 개발을 위해 SDK와 도구 구성이 필요하다고 안내하며, 이 상태를 확인하는 가장 쉬운 출발점이 flutter doctor입니다.1
조금 더 자세히 보고 싶다면 -v 옵션을 붙입니다.
flutter doctor -v입문자가 자주 놓치는 점은 flutter doctor 결과 전체를 한 번에 고치려고 한다는 것입니다. 그러면 더 헷갈립니다. 먼저 빨간색 ✗ 또는 느낌표 [!]가 붙은 항목 중 지금 실행하려는 플랫폼과 관련된 것부터 봅니다. Android 앱을 실행하려는데 Xcode 항목이 경고라고 해서 당장 막히는 것은 아닙니다. 반대로 iPhone 시뮬레이터에서 실행하려는데 Android license 경고를 먼저 붙잡고 있을 필요도 없습니다.
문제 해결의 기본 흐름은 단순합니다.
flutter doctor
# 표시된 첫 번째 핵심 문제를 고친다
flutter doctor
# 해결됐는지 다시 확인한다이 과정을 반복합니다. 의사에게 한 번 진료받고 약을 먹은 뒤 다시 검사하듯, 개발 환경도 수정 후 재검사가 필요합니다.
Android license status unknown 해결하기
Flutter 입문자가 가장 흔히 만나는 문장 중 하나가 이것입니다.
Android license status unknown.
Run flutter doctor --android-licenses to accept the SDK licenses.또는 빌드 중에 다음처럼 나올 수 있습니다.
You have not accepted the license agreements of the following SDK components.이 메시지는 Android SDK 자체가 없다는 뜻이 아니라, 대개 Android SDK를 사용하기 위한 라이선스 동의가 끝나지 않았다는 뜻입니다. 가장 먼저 다음 명령어를 실행합니다.
flutter doctor --android-licenses약관이 여러 번 나오면 끝까지 y를 입력합니다. 마지막에 다음과 비슷한 문장이 나오면 정상입니다.
All SDK package licenses accepted.그다음 반드시 다시 확인합니다.
flutter doctor여기까지로 해결되는 경우가 많습니다. 하지만 현실은 늘 그렇게 친절하지 않습니다. 어떤 환경에서는 flutter doctor --android-licenses 자체가 실패합니다. 이때부터는 라이선스 문제가 아니라 Android SDK 위치나 Command-line Tools 문제일 가능성이 큽니다.
android-licenses 명령이 실패할 때
다음과 같은 증상이 있다면 Android SDK 구성을 확인해야 합니다.
No Android SDK found.sdkmanager not found.java.lang.NoClassDefFoundError이때는 아래 순서대로 점검합니다.
Android SDK 위치 확인
Android Studio를 열고 SDK 위치를 확인합니다.
Windows와 Linux에서는 보통 다음 메뉴를 사용합니다.
File > Settings > Appearance & Behavior > System Settings > Android SDKmacOS에서는 다음 경로로 들어갑니다.
Android Studio > Settings 또는 Preferences > Appearance & Behavior > System Settings > Android SDK여기서 Android SDK Location을 봅니다. 일반적인 위치는 다음과 비슷합니다.
Windows: C:Users사용자명AppDataLocalAndroidSdk
macOS: /Users/사용자명/Library/Android/sdk
Linux: /home/사용자명/Android/SdkFlutter가 다른 SDK 위치를 보고 있다면 다음 명령어로 명시할 수 있습니다.
flutter config --android-sdk /path/to/android/sdk설정 후 다시 확인합니다.
flutter doctor -vAndroid SDK Command-line Tools 설치
flutter doctor --android-licenses가 실행되지 않는 대표 원인은 Android SDK Command-line Tools가 설치되어 있지 않은 경우입니다. Android Studio의 SDK Manager에서 SDK Tools 탭을 열고 다음 항목을 확인합니다.
Android SDK Command-line Tools 최신 버전
Android SDK Platform-Tools
Android SDK Build-Tools
Android Emulator
특히 Command-line Tools는 라이선스 승인과 sdkmanager 실행에 직접 관련됩니다. 설치 후 터미널을 새로 열고 다시 실행합니다.
flutter doctor --android-licenses환경 변수 확인
SDK는 설치되어 있는데 Flutter가 찾지 못한다면 환경 변수를 확인합니다. macOS나 Linux에서는 ~/.zshrc, ~/.bashrc, ~/.profile 중 사용하는 셸 설정 파일에 다음처럼 추가할 수 있습니다.
export ANDROID_HOME=/Users/사용자명/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/binWindows에서는 시스템 환경 변수에 ANDROID_HOME을 추가하고, Path에 platform-tools와 cmdline-toolslatestbin을 추가합니다.
터미널을 다시 연 뒤 확인합니다.
echo $ANDROID_HOME
sdkmanager --versionWindows PowerShell에서는 다음처럼 확인할 수 있습니다.
echo $env:ANDROID_HOME
sdkmanager --version그래도 flutter doctor --android-licenses가 실패한다면 sdkmanager로 직접 라이선스를 승인해 봅니다.
sdkmanager --licenses이 경우에도 약관마다 y를 입력하고, 마지막에는 다시 Flutter 쪽 진단을 봅니다.
flutter doctorAndroid 빌드 실패: Gradle, AGP, JDK를 함께 보자
Android 빌드 실패는 초보자에게 가장 무섭게 보입니다. 로그가 길고, 영어가 많고, FAILURE: Build failed with an exception.이라는 문장이 너무 단호하기 때문입니다. 하지만 이 문장은 결론일 뿐입니다. 진짜 원인은 보통 그 아래나 위에 있는 첫 번째 What went wrong 또는 Caused by 근처에 있습니다.
먼저 긴 로그에서 다음 단어를 찾습니다.
What went wrong
Caused by
Minimum supported Gradle version
Unsupported class file major version
compileSdk
minSdkVersion
Kotlin
Android Gradle pluginAndroid 빌드는 여러 버전이 서로 맞아야 합니다. Flutter 프로젝트의 Dart 코드가 맞더라도 Gradle, Android Gradle Plugin, Kotlin, JDK, Android SDK 버전이 서로 어긋나면 빌드가 실패합니다.
Unsupported class file major version
다음과 같은 메시지는 JDK와 Gradle 버전 불일치일 가능성이 큽니다.
Unsupported class file major version 61여기서 61은 Java 17과 관련된 값입니다. 쉽게 말하면 '현재 Gradle이 이 Java 버전을 이해하지 못한다'는 신호입니다. 해결 방향은 둘 중 하나입니다.
Gradle과 Android Gradle Plugin을 현재 JDK에 맞게 올린다.
Flutter가 사용하는 JDK를 프로젝트가 감당할 수 있는 버전으로 낮춘다.
Android Studio에 포함된 JDK 위치와 터미널의
java --version결과를 비교한다.
현재 Flutter가 어떤 Java를 쓰는지 봅니다.
flutter doctor -v
java --versionFlutter에서 사용할 JDK를 직접 지정해야 할 때는 다음 명령어를 사용할 수 있습니다.
flutter config --jdk-dir /path/to/jdk이후 다시 확인합니다.
flutter doctor -vGradle과 Android Gradle Plugin 버전 확인
프로젝트마다 파일 구조가 조금 다릅니다. 최신 Flutter 프로젝트는 settings.gradle 또는 settings.gradle.kts에서 플러그인 버전을 관리하는 경우가 많고, 예전 프로젝트는 android/build.gradle에서 관리하는 경우가 많습니다. 그래서 한 파일만 보고 끝내면 안 됩니다.
확인할 파일은 보통 다음과 같습니다.
android/settings.gradle
android/build.gradle
android/app/build.gradle
android/gradle/wrapper/gradle-wrapper.properties
android/gradle.propertiesGradle Wrapper 버전은 대개 이 파일에서 봅니다.
android/gradle/wrapper/gradle-wrapper.properties예시는 다음과 같습니다.
distributionUrl=https://services.gradle.org/distributions/gradle-8.7-all.zipAndroid Gradle Plugin 버전은 프로젝트 구조에 따라 다음 중 한 곳에 있습니다.
plugins {
id 'com.android.application' version '8.5.0' apply false
}또는 예전 방식에서는 다음처럼 보입니다.
classpath 'com.android.tools.build:gradle:8.5.0'중요한 것은 '최신으로 다 올리기'가 아니라 '서로 맞는 조합으로 맞추기'입니다. 빌드 도구는 악기 합주와 같습니다. 바이올린만 최신이어도 피아노가 다른 곡을 치면 연주는 망가집니다.
compileSdk와 minSdk 문제
플러그인을 추가한 뒤 다음과 비슷한 에러가 날 수 있습니다.
uses-sdk:minSdkVersion 21 cannot be smaller than version 23 declared in library이 메시지는 어떤 플러그인이 더 높은 Android 최소 버전을 요구한다는 뜻입니다. android/app/build.gradle에서 minSdk를 확인합니다.
android {
defaultConfig {
minSdk = 23
}
}또는 예전 프로젝트라면 다음처럼 되어 있을 수 있습니다.
minSdkVersion 23compileSdk 관련 에러가 난다면 설치된 Android SDK Platform과 프로젝트의 compileSdk 값을 함께 봅니다. Android Studio의 SDK Manager에서 해당 API 버전이 설치되어 있는지도 확인해야 합니다.
캐시가 꼬였을 때의 안전한 초기화
설정을 고쳤는데 계속 예전 에러가 난다면 캐시 문제일 수 있습니다. 먼저 프로젝트 루트에서 다음을 실행합니다.
flutter clean
flutter pub getAndroid 쪽도 정리합니다.
cd android
./gradlew clean
cd ..Windows에서는 다음처럼 실행합니다.
cd android
.gradlew clean
cd ..그래도 같은 Gradle 에러가 반복된다면 전역 Gradle 캐시가 꼬였을 가능성도 있습니다. 이때는 신중하게 ~/.gradle/caches 또는 Windows의 C:Users사용자명.gradlecaches를 정리할 수 있습니다. 다만 이 작업은 다음 빌드 때 의존성을 다시 많이 다운로드하므로 시간이 걸립니다.
iOS 빌드 실패: Xcode와 CocoaPods부터 나누기
iOS 빌드 실패는 크게 세 층으로 나누면 이해하기 쉽습니다.
Xcode 설치와 Command Line Tools 문제
CocoaPods 의존성 설치 문제
Signing, Bundle Identifier, Provisioning 문제
먼저 진단합니다.
flutter doctor -vXcode 항목에 문제가 있다면 Xcode가 제대로 설치되어 있는지, Command Line Tools가 연결되어 있는지 확인합니다.
xcodebuild -version
xcode-select -pCommand Line Tools가 없거나 깨졌다면 다음 명령어를 실행합니다.
xcode-select --installCocoaPods가 없거나 깨졌을 때
다음 메시지는 iOS 입문자가 정말 자주 만납니다.
CocoaPods not installed or not in valid state.먼저 설치 여부를 확인합니다.
pod --version명령어를 찾을 수 없다면 CocoaPods를 설치해야 합니다. Homebrew를 사용한다면 다음처럼 설치할 수 있습니다.
brew install cocoapodsRubyGems를 사용한다면 다음처럼 설치할 수 있습니다.
gem install cocoapods예전 자료에서는 sudo gem install cocoapods를 많이 사용하지만, macOS 권한 문제를 만들 수 있으므로 가능하면 Homebrew나 사용자 영역 설치를 먼저 고려합니다. 이미 설치되어 있는데도 깨졌다고 나오면 CocoaPods가 설치된 Ruby 버전과 현재 실행 중인 Ruby 버전이 달라진 경우일 수 있습니다. 이때는 CocoaPods를 재설치하거나, 팀 프로젝트라면 Bundler로 CocoaPods 버전을 고정하는 방법도 좋습니다.
pod install 실패 해결 루틴
Firebase, 카메라, 위치, 알림 같은 플러그인을 추가하면 iOS 쪽 native 의존성이 함께 생깁니다. Flutter 패키지는 flutter pub get으로 받지만, iOS 네이티브 의존성은 CocoaPods가 처리합니다. Firebase 공식 Flutter 설정 문서도 FlutterFire CLI와 Firebase 플러그인 추가 후 앱을 다시 빌드하는 흐름을 안내합니다.2
다음 메시지가 보이면 CocoaPods 의존성 해결 문제일 가능성이 큽니다.
CocoaPods could not find compatible versions for pod
Specs satisfying the dependency were found, but they required a higher minimum deployment target
Error running pod install가장 먼저 안전한 초기화 루틴을 실행합니다.
flutter clean
rm -rf ios/Pods
rm -f ios/Podfile.lock
flutter pub get
cd ios
pod install --repo-update
cd ..
flutter run이 루틴은 실전에서 매우 자주 통합니다. Pods는 설치된 iOS 의존성 폴더이고, Podfile.lock은 특정 버전으로 해석된 결과입니다. 패키지를 바꾸거나 Xcode를 업데이트했는데 오래된 lock이 남아 있으면 충돌이 날 수 있습니다. iOS 빌드 실패는 Podfile, Pods, DerivedData, 도구 버전이 함께 얽히는 경우가 많으므로 단계적으로 초기화하는 접근이 효과적입니다.3
그래도 해결되지 않는다면 Xcode 캐시도 지워 봅니다.
rm -rf ~/Library/Developer/Xcode/DerivedData그리고 다시 실행합니다.
flutter clean
flutter pub get
cd ios
pod install --repo-update
cd ..
flutter build ios --debugiOS 최소 버전 문제
다음 메시지는 어떤 플러그인이 더 높은 iOS 최소 버전을 요구한다는 뜻입니다.
required a higher minimum deployment targetios/Podfile에서 platform 값을 확인합니다.
platform :ios, '13.0'값을 올리면 빌드는 해결될 수 있지만, 그만큼 지원 가능한 오래된 iOS 기기가 줄어듭니다. 사이드 프로젝트라면 큰 문제가 아닐 수 있지만, 실제 서비스라면 사용자 통계를 보고 결정해야 합니다.
Xcode에서 열어야 하는 파일
Flutter iOS 프로젝트를 Xcode로 열 때는 보통 다음 파일을 열어야 합니다.
ios/Runner.xcworkspaceRunner.xcodeproj를 열면 CocoaPods가 연결한 의존성이 제대로 보이지 않아 Module not found 같은 에러가 날 수 있습니다. Xcode에서 iOS 설정을 수정할 때는 이 차이를 꼭 기억하세요.
Signing과 Bundle Identifier
실제 iPhone에서 실행하거나 App Store 배포 빌드를 만들 때는 Signing 문제가 자주 발생합니다.
Signing for Runner requires a development team.
No profiles for bundle identifier were found.이 경우에는 Xcode에서 Runner.xcworkspace를 열고 Runner 타깃의 Signing & Capabilities로 들어갑니다. Team을 선택하고, Bundle Identifier가 Apple Developer 계정에서 사용할 수 있는 고유한 값인지 확인합니다.
예를 들어 다음처럼 너무 흔한 값은 충돌할 수 있습니다.
com.example.myapp실제 프로젝트라면 다음처럼 자신이나 조직의 도메인 기반으로 정합니다.
com.yourname.knowledgeNote시뮬레이터에서는 잘 되는데 실제 기기에서만 실패한다면 거의 항상 Signing, 권한, 프로비저닝 프로파일 쪽을 봐야 합니다.
Apple Silicon에서만 실패할 때
M1, M2, M3, M4 같은 Apple Silicon Mac에서는 시뮬레이터 아키텍처 문제로 특정 Pod가 실패할 수 있습니다. 에러에 다음 단어가 보이면 아키텍처 문제를 의심합니다.
arm64
x86_64
Undefined symbols for architecture arm64일부 오래된 Pod에서는 시뮬레이터 arm64를 제외하는 임시 처방이 사용되기도 합니다. 다만 이 설정은 장기적인 해결책이라기보다 '문제 있는 의존성을 업데이트하기 전까지 쓰는 우회로'에 가깝습니다. 가능하면 플러그인과 Pod 버전을 먼저 업데이트하고, 그래도 안 될 때 Podfile의 post_install 설정을 검토합니다.
flutter pub get 실패와 dependency conflict
flutter pub get은 Dart와 Flutter 패키지를 내려받고 버전을 맞추는 명령어입니다. 그런데 패키지가 늘어날수록 다음 메시지를 볼 가능성이 커집니다.
version solving failed이 말은 pub이 '모든 패키지가 만족하는 버전 조합을 찾지 못했다'는 뜻입니다. Flutter의 pubspec.yaml은 프로젝트 이름, SDK 제약, dependencies, assets, fonts 같은 정보를 담는 파일이며, 첫 빌드 후 생성되는 pubspec.lock은 실제 선택된 패키지 버전을 기록합니다.4
문제를 볼 때는 먼저 pubspec.yaml의 SDK 제약을 확인합니다.
environment:
sdk: ^3.11.0현재 Flutter와 Dart 버전을 확인합니다.
flutter --version
dart --version패키지가 요구하는 Dart SDK가 현재보다 높다면 Flutter를 업그레이드해야 할 수 있습니다.
flutter upgrade반대로 팀 프로젝트에서 특정 Flutter 버전을 써야 한다면 FVM 같은 버전 관리 도구를 사용해 프로젝트별 Flutter 버전을 맞추는 것이 좋습니다.
충돌 메시지 읽는 법
다음과 같은 메시지를 봅시다.
Because package_a depends on package_b ^3.0.0 and package_c depends on package_b ^2.0.0, version solving failed.이 문장은 package_a는 package_b 3.x를 원하고, package_c는 package_b 2.x를 원해서 둘을 동시에 만족시킬 수 없다는 뜻입니다. Dart 패키지 의존성 문서에서도 직접 의존성뿐 아니라 다른 패키지가 끌고 오는 transitive dependency의 제약이 서로 맞지 않을 수 있다고 설명합니다.5
이럴 때 무작정 모든 버전을 최신으로 올리면 더 큰 충돌이 날 수 있습니다. 반대로 버전을 전부 지우고 any를 쓰는 것도 추천하지 않습니다. 당장은 통과할 수 있지만, 다음 빌드에서 전혀 다른 버전이 들어와 문제가 재현되지 않는 지옥이 열릴 수 있습니다.
먼저 현재 의존성을 봅니다.
flutter pub deps오래된 패키지를 확인합니다.
flutter pub outdated그다음 충돌하는 패키지의 pub.dev 페이지에서 Flutter SDK, Dart SDK, 다른 패키지와의 호환 범위를 확인합니다. 버전은 '최신'이 아니라 '현재 프로젝트와 호환되는 안정 버전'을 고르는 것이 핵심입니다.
수정 후에는 다음 순서로 정리합니다.
flutter clean
flutter pub get그래도 pub 캐시가 이상하다면 다음 명령어를 사용할 수 있습니다.
flutter pub cache repair다만 이 명령어는 시간이 걸릴 수 있으므로, 모든 문제의 첫 번째 해결책으로 남발하지는 않습니다.
Firebase 설정 에러
Firebase를 붙인 뒤 Android나 iOS 빌드가 깨진다면 대개 구성 파일 위치나 초기화 코드 문제입니다. Firebase 공식 문서는 FlutterFire CLI를 설치하고 flutterfire configure로 플랫폼별 Firebase 앱을 연결하며, firebase_options.dart를 생성한 뒤 Firebase.initializeApp으로 초기화하는 흐름을 안내합니다.2
Android에서 자주 놓치는 파일은 다음입니다.
android/app/google-services.jsoniOS에서 자주 놓치는 파일은 다음입니다.
ios/Runner/GoogleService-Info.plistFlutterFire CLI를 사용했다면 보통 다음 파일도 생성됩니다.
lib/firebase_options.dart초기화 코드는 앱 시작 전에 실행되어야 합니다.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}자주 만나는 실수는 다음과 같습니다.
firebase_core를 추가하지 않았다.flutterfire configure후 다시 빌드하지 않았다.Android 패키지 이름 또는 iOS Bundle Identifier가 Firebase 콘솔의 앱 설정과 다르다.
google-services.json또는GoogleService-Info.plist를 잘못된 위치에 넣었다.DefaultFirebaseOptions를 import하지 않았다.
Firebase 플러그인을 새로 추가했다면 다음을 한 번 더 실행하는 습관을 들이면 좋습니다.
flutterfire configure
flutter clean
flutter pub get
flutter runNull Safety 에러
Dart의 null safety는 '값이 없을 수 있음'을 코드 차원에서 명확히 다루는 기능입니다. 처음에는 귀찮아 보이지만, 앱이 갑자기 null 때문에 죽는 일을 크게 줄여 줍니다.
자주 보는 에러는 다음과 같습니다.
The argument type String? can't be assigned to the parameter type String.String?은 null일 수도 있는 문자열이고, String은 null이 아닌 문자열입니다. null일 수 있는 값을 null이 불가능한 곳에 넣으려 하니 Dart가 막는 것입니다.
예를 들어 다음 코드는 실패할 수 있습니다.
String? name;
Text(name);해결 방법은 상황에 따라 다릅니다. 기본값을 줄 수 있다면 ??를 사용합니다.
Text(name ?? '이름 없음');정말 null이 아님을 확신한다면 !를 붙일 수 있습니다.
Text(name!);하지만 !는 안전벨트를 자르는 칼과 같습니다. 컴파일러에게 '내가 책임질게'라고 말하는 것이므로, 실제로 null이면 런타임에서 앱이 터집니다. 가능하면 기본값, 조건문, 명확한 상태 모델로 처리하세요.
if (name == null) {
return const Text('이름을 불러오는 중입니다');
}
return Text(name);Type mismatch와 import 충돌
Dart에서 다음 에러도 자주 만납니다.
A value of type List<dynamic> can't be assigned to a variable of type List<Note>.대개 JSON을 파싱하거나 API 응답을 모델로 바꿀 때 발생합니다. Dart는 dynamic을 알아서 Note로 바꿔 주지 않습니다. 명시적으로 변환해야 합니다.
final notes = jsonList
.map((item) => Note.fromJson(item as Map<String, dynamic>))
.toList();또 다른 흔한 문제는 import 충돌입니다. 예를 들어 서로 다른 패키지에 같은 이름의 클래스가 있을 수 있습니다. 이때는 alias를 사용할 수 있습니다.
import 'package:my_app/models/user.dart' as app_model;
import 'package:firebase_auth/firebase_auth.dart' as auth;이후 코드에서 명확히 구분합니다.
final auth.User? firebaseUser = auth.FirebaseAuth.instance.currentUser;
final appUser = app_model.User(id: firebaseUser?.uid ?? '');RenderFlex overflow 해결하기
Flutter UI를 만들다 보면 노란색과 검은색 줄무늬가 화면에 나타납니다. 처음 보면 앱이 공사장에 들어간 것 같지만, 이건 Flutter가 아주 친절하게 '여기 공간이 부족해요'라고 알려 주는 표시입니다.
대표 메시지는 다음과 같습니다.
A RenderFlex overflowed by 42 pixels on the bottom.Row나 Column 안의 자식 위젯들이 화면보다 커졌다는 뜻입니다. 해결 방법은 원인에 따라 다릅니다.
텍스트가 가로로 넘친다면 Expanded를 사용합니다.
Row(
children: [
const Icon(Icons.note),
Expanded(
child: Text(
note.title,
overflow: TextOverflow.ellipsis,
),
),
],
)세로 콘텐츠가 화면보다 길다면 스크롤을 고려합니다.
SingleChildScrollView(
child: Column(
children: [
// 긴 폼 또는 콘텐츠
],
),
)Column 안에 ListView를 바로 넣어서 문제가 생겼다면 Expanded로 남은 공간을 지정합니다.
Column(
children: [
const SearchBar(),
Expanded(
child: ListView.builder(
itemCount: notes.length,
itemBuilder: (context, index) {
return NoteTile(note: notes[index]);
},
),
),
],
)RenderFlex overflow는 Flutter 초보자의 통과의례입니다. 이 에러를 만났다는 것은 이제 레이아웃 제약을 몸으로 배우기 시작했다는 뜻입니다.
setState, context, mounted 관련 에러
비동기 작업 후 화면을 업데이트할 때 다음 에러를 볼 수 있습니다.
setState called after dispose화면이 이미 사라졌는데, 늦게 도착한 Future가 setState를 호출한 상황입니다. 예를 들어 API 요청 중 사용자가 뒤로 가기를 눌렀다면 이런 일이 생길 수 있습니다.
해결은 mounted 확인입니다.
Future<void> loadData() async {
final result = await repository.fetchNotes();
if (!mounted) return;
setState(() {
notes = result;
});
}BuildContext를 비동기 이후 사용하는 경우에도 비슷한 문제가 생깁니다. 예를 들어 await 뒤에 Navigator를 호출한다면 현재 위젯이 아직 살아 있는지 확인해야 합니다.
await saveNote();
if (!context.mounted) return;
Navigator.pop(context);이 작은 습관 하나가 이상한 런타임 에러를 많이 줄여 줍니다.
Provider, Riverpod 관련 상태관리 에러
상태관리 패키지를 쓰다 보면 다음과 같은 에러가 나올 수 있습니다.
ProviderNotFoundException또는 Riverpod에서는 provider scope가 없다는 식의 메시지를 볼 수 있습니다. 이런 에러는 대부분 '상태를 제공하는 위젯보다 위에서 상태를 읽으려고 했다'는 뜻입니다.
Provider를 쓴다면 ChangeNotifierProvider가 해당 화면보다 위에 있어야 합니다.
ChangeNotifierProvider(
create: (_) => NoteController(),
child: const NoteApp(),
)Riverpod을 쓴다면 앱 전체를 ProviderScope로 감싸야 합니다.
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}상태관리 에러는 코드가 어려워서라기보다 '위젯 트리의 위치'가 맞지 않아서 생기는 경우가 많습니다. 상태를 읽는 곳이 어디인지, 상태를 제공하는 곳이 어디인지 트리 그림을 그려 보면 빠르게 해결됩니다.
에뮬레이터가 실행되지 않을 때
Android 에뮬레이터가 실행되지 않거나 매우 느릴 때는 앱 코드보다 PC 설정 문제일 가능성이 큽니다. 먼저 디바이스 목록을 봅니다.
flutter devicesAndroid 쪽 기기 상태는 adb로도 확인할 수 있습니다.
adb devices에뮬레이터가 아예 켜지지 않는다면 Android Studio의 Device Manager에서 새 가상 기기를 만들어 봅니다. 특히 Windows에서는 BIOS 또는 UEFI에서 가상화 기능이 꺼져 있으면 에뮬레이터가 제대로 동작하지 않을 수 있습니다. Intel CPU는 VT-x, AMD CPU는 AMD-V 같은 이름으로 표시됩니다.
느린 에뮬레이터를 쓸 때는 해상도를 낮추고, 무거운 앱을 닫고, 가능하면 x86_64 시스템 이미지를 사용합니다. 실제 기기가 있다면 USB 디버깅을 켜고 직접 연결해 테스트하는 것도 좋은 방법입니다. 입문 단계에서는 실제 기기 테스트가 에뮬레이터 문제를 우회하는 가장 빠른 길일 때가 많습니다.
실제 디바이스가 인식되지 않을 때
실제 Android 기기가 보이지 않으면 다음을 확인합니다.
개발자 옵션이 켜져 있는가
USB 디버깅이 켜져 있는가
휴대폰 화면에 뜬 RSA 디버깅 허용 팝업을 승인했는가
USB 케이블이 충전 전용이 아니라 데이터 전송을 지원하는가
Windows라면 제조사 USB 드라이버가 설치되어 있는가
명령어로는 다음을 사용합니다.
flutter devices
adb devicesadb devices에서 unauthorized라고 나오면 휴대폰 화면을 확인하세요. 디버깅 허용 팝업이 숨어 있을 가능성이 큽니다. 팝업이 안 뜨면 개발자 옵션에서 USB 디버깅 권한을 취소한 뒤 다시 연결해 봅니다.
iPhone이 인식되지 않을 때는 Xcode에서 기기를 먼저 인식하는지 확인합니다. 케이블 연결 후 iPhone에서 '이 컴퓨터를 신뢰하겠습니까?'를 승인해야 합니다. 실제 기기 실행은 Apple Developer 계정, Signing 설정, Bundle Identifier까지 함께 맞아야 하므로 시뮬레이터보다 점검할 것이 더 많습니다.
Hot Reload가 이상하게 동작할 때
Hot Reload는 Flutter의 강력한 장점이지만, 모든 변경을 반영하지는 않습니다. 예를 들어 앱 시작 시 한 번만 실행되는 초기화 코드, main() 함수 내부 설정, 네이티브 플랫폼 설정, asset 추가, 플러그인 추가는 Hot Reload만으로 반영되지 않을 수 있습니다.
이때는 단계적으로 강도를 올립니다.
# 먼저 Hot Restart
r
# 그래도 안 되면 앱을 완전히 종료 후 재실행
flutter run
# 패키지나 네이티브 설정을 바꿨다면
flutter clean
flutter pub get
flutter runasset을 추가했는데 이미지가 안 보인다면 pubspec.yaml 들여쓰기도 확인해야 합니다. YAML은 공백에 민감합니다. 탭 대신 스페이스를 사용하고, 파일 경로가 실제 경로와 정확히 일치하는지 확인합니다.
flutter:
assets:
- assets/images/이런 설정은 Hot Reload가 아니라 재시작이 필요한 경우가 많습니다.
에러 로그 읽는 습관
에러 로그를 읽을 때는 처음부터 끝까지 번역하려고 하지 마세요. 먼저 키워드를 잡습니다. 아래처럼 접근하면 훨씬 빠릅니다.
1. 첫 번째 Error 또는 Exception을 찾는다.
2. What went wrong 또는 Caused by 근처를 본다.
3. 파일 경로가 lib/인지 android/인지 ios/인지 확인한다.
4. 최근에 바꾼 코드, 패키지, 설정을 떠올린다.
5. 수정 후 같은 명령어로 재현 여부를 확인한다.파일 경로가 lib/라면 Dart나 Flutter 코드 문제일 가능성이 큽니다. android/라면 Gradle, SDK, JDK, Android 설정을 봅니다. ios/라면 Xcode, CocoaPods, Signing, Podfile을 봅니다. .pub-cache가 보이면 외부 패키지 또는 그 패키지의 네이티브 코드가 관련될 수 있습니다.
그리고 정말 중요한 원칙이 있습니다. 에러를 고칠 때 한 번에 여러 가지를 바꾸지 마세요. Gradle 버전도 바꾸고, Flutter도 업그레이드하고, 패키지도 전부 올리고, 캐시도 지우면 운 좋게 해결될 수는 있지만 왜 해결됐는지 알 수 없습니다. 다음에 같은 문제가 오면 다시 처음부터 헤매게 됩니다.
실전 응급 명령어 모음
상황별로 자주 쓰는 명령어를 정리해 둡니다. 무조건 위에서부터 전부 실행하는 것이 아니라, 현재 문제가 어느 층인지 보고 골라 쓰세요.
Flutter 기본 진단입니다.
flutter doctor
flutter doctor -v
flutter devices
flutter --version
dart --version패키지와 캐시 정리입니다.
flutter pub get
flutter pub outdated
flutter pub deps
flutter clean
flutter pub cache repairAndroid 빌드 정리입니다.
flutter doctor --android-licenses
flutter config --android-sdk /path/to/android/sdk
cd android
./gradlew clean
cd ..
flutter build apkWindows에서는 Gradle 명령어가 다릅니다.
cd android
.gradlew clean
cd ..
flutter build apkiOS 빌드 정리입니다.
flutter clean
rm -rf ios/Pods
rm -f ios/Podfile.lock
rm -rf ~/Library/Developer/Xcode/DerivedData
flutter pub get
cd ios
pod install --repo-update
cd ..
flutter build ios --debugFirebase 설정 확인입니다.
dart pub global activate flutterfire_cli
flutterfire configure
flutter pub add firebase_core
flutter run문제 해결 체크리스트
마지막으로, 실제 에러를 만났을 때 따라갈 수 있는 짧은 흐름을 남깁니다.
먼저 환경 문제인지 코드 문제인지 나눕니다. 새 프로젝트도 실행되지 않으면 환경 문제일 가능성이 큽니다. 현재 프로젝트만 안 된다면 패키지, 네이티브 설정, 최근 변경 코드가 원인일 가능성이 큽니다.
flutter create error_test
cd error_test
flutter run새 프로젝트가 잘 실행된다면 내 프로젝트의 pubspec.yaml, android/, ios/ 설정을 봅니다. 새 프로젝트도 실패한다면 Flutter SDK, Android Studio, Xcode, 디바이스 연결, PATH 같은 환경을 봅니다.
빌드 실패라면 로그의 첫 번째 원인을 찾습니다. 마지막 줄만 보지 말고 What went wrong, Caused by, version solving failed, required a higher minimum deployment target 같은 핵심 문장을 찾습니다.
의존성 문제라면 pubspec.yaml과 pubspec.lock을 함께 봅니다. SDK constraint, 직접 추가한 패키지, transitive dependency를 확인하고 flutter pub outdated로 가능한 업그레이드 범위를 봅니다.
Android 문제라면 flutter doctor, Android SDK 위치, Command-line Tools, Android license, Gradle, AGP, JDK, compileSdk, minSdk를 순서대로 확인합니다.
iOS 문제라면 flutter doctor, Xcode Command Line Tools, CocoaPods, Podfile, Podfile.lock, Runner.xcworkspace, Signing, Bundle Identifier, Simulator 상태를 순서대로 확인합니다.
UI 문제라면 에러가 말하는 위젯을 찾습니다. RenderFlex overflow는 공간 문제, setState called after dispose는 생명주기 문제, Provider 관련 에러는 위젯 트리 위치 문제인 경우가 많습니다.
에러를 기록하는 개발자가 빨리 성장한다
에러를 해결하고 나면 그냥 넘어가고 싶어집니다. 하지만 같은 문제는 언젠가 다시 옵니다. 그래서 자신만의 작은 에러 노트를 만들면 좋습니다. 다음 네 가지를 적어 두세요.
에러 메시지의 핵심 문장
발생한 상황과 직전에 바꾼 것
실제로 효과가 있었던 해결 방법
다시 발생하지 않도록 남긴 설정 또는 주의점
Flutter 개발자는 위젯을 잘 다루는 사람만이 아닙니다. 빌드 로그를 읽고, 도구의 층을 구분하고, 재현 가능한 방식으로 문제를 좁혀 가는 사람입니다. 에러는 개발을 멈추게 하는 벽처럼 보이지만, 사실은 앱 개발의 내부 구조를 보여 주는 창입니다.
이 장의 목표는 모든 에러를 외우는 것이 아닙니다. 에러를 만났을 때 '이제 끝났다'가 아니라 '어느 층부터 보면 되지?'라고 생각할 수 있게 되는 것입니다. 그 순간부터 여러분은 단순히 예제를 따라 치는 입문자를 넘어, 스스로 앱을 끝까지 완성할 수 있는 개발자에 가까워집니다.
참고
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.
