Flutter 프로젝트를 오래 유지하다 보면 flutter analyze가 수백 개의 deprecated 경고를 뱉는 시점이 온다. 기능은 잘 돌아가지만 Warning이 쌓이면 진짜 문제가 묻힌다. 이번에 한 번에 200개 넘는 deprecated 경고를 정리하면서 나온 패턴들을 정리한다.
진단: flutter analyze로 현황 파악
flutter analyze --no-pub
--no-pub을 붙이면 pub 패키지 재분석을 건너뛰어 빠르다. 출력에서 카테고리별로 분류해보면 대부분 몇 가지 패턴이 반복된다.
info • 'withOpacity' is deprecated ... • lib/core/theme/app_theme.dart:45:22
info • 'value' is deprecated and shouldn't be used. Use 'initialValue' instead ...
info • 'activeColor' is deprecated ... Use 'activeThumbColor' instead.
warning • Unused import: 'package:go_router/go_router.dart' ...
케이스 1: Color.withOpacity → withValues(alpha:)
가장 많이 나오는 deprecation이다. 거의 모든 색상 투명도 처리 코드가 해당된다.
변경 전:
color: Colors.blue.withOpacity(0.5)
color: theme.accent.withOpacity(0.16)
border: Border.all(color: colors.border.withOpacity(0.3))
변경 후:
color: Colors.blue.withValues(alpha: 0.5)
color: theme.accent.withValues(alpha: 0.16)
border: Border.all(color: colors.border.withValues(alpha: 0.3))
파일이 많으면 sed로 한 번에 바꾼다.
# 프로젝트 전체 일괄 치환 (macOS)
find lib -name "*.dart" -exec sed -i '' 's/\.withOpacity(\([^)]*\))/.withValues(alpha: \1)/g' {} \;
치환 후 flutter analyze로 다시 확인한다. 간혹 변수명이 withOpacity인 메서드를 직접 구현한 경우 오탐이 생기니 결과를 꼭 검토한다.
케이스 2: DropdownButtonFormField.value → initialValue
Flutter 3.x 이후 DropdownButtonFormField의 초기값 설정 파라미터명이 바뀌었다.
변경 전:
DropdownButtonFormField<String>(
value: _selectedCategory,
items: ...,
onChanged: (v) => setState(() => _selectedCategory = v),
)
변경 후:
DropdownButtonFormField<String>(
initialValue: _selectedCategory,
items: ...,
onChanged: (v) => setState(() => _selectedCategory = v),
)
value와 initialValue는 동작 방식에 미묘한 차이가 있다. value는 controlled 방식으로 외부 상태와 항상 동기화되고, initialValue는 초기값만 지정한다. 대부분의 경우 initialValue로 바꿔도 동작이 같다.
케이스 3: Switch.activeColor → activeThumbColor
Switch 위젯의 색상 프로퍼티들이 Material 3 기준으로 세분화됐다.
변경 전:
Switch(
value: _isEnabled,
onChanged: _onToggle,
activeColor: Colors.blue,
)
변경 후:
Switch(
value: _isEnabled,
onChanged: _onToggle,
activeThumbColor: Colors.blue,
)
트랙 색상을 함께 바꾸고 싶다면 activeTrackColor도 추가한다. 기존 activeColor는 thumb과 track 모두 같은 색으로 설정했지만, 새 API는 분리되어 있다.
케이스 4: GoRouter location → uri.toString()
GoRouter에서 현재 경로를 가져올 때 .location 대신 .uri를 써야 한다.
변경 전:
final currentPath = GoRouter.of(context)
.routeInformationProvider
.value
.location;
변경 후:
final currentPath = GoRouter.of(context)
.routeInformationProvider
.value
.uri
.toString();
location은 String이었고, uri는 Uri 객체다. 경로 비교나 startsWith 같은 용도라면 toString() 없이 uri.path를 쓰는 게 더 적절하다.
케이스 5: BuildContext async gap 경고
async 함수 내에서 await 이후 context를 사용하면 경고가 난다. 위젯이 dispose된 후 context를 참조할 수 있기 때문이다.
문제 코드:
Future<void> _onPickImage() async {
final result = await ImagePicker().pickImage(source: ImageSource.gallery);
if (result != null) {
context.read<SomeBloc>().add(ImageSelected(result.path)); // ⚠️
}
}
수정:
Future<void> _onPickImage() async {
final result = await ImagePicker().pickImage(source: ImageSource.gallery);
if (!mounted) return; // ← 추가
if (result != null) {
context.read<SomeBloc>().add(ImageSelected(result.path));
}
}
await 직후 if (!mounted) return;을 추가하는 게 표준 패턴이다.
케이스 6: 기타 자잘한 경고들
불필요한 string interpolation:
// 전 (경고)
Text('${someVariable}')
Text('?id=${widget.id}') // 중괄호 불필요
// 후
Text('$someVariable')
Text('?id=$widget.id') // 단, 프로퍼티 접근 시엔 중괄호 필요
Text('?id=${widget.id}') // 이 경우엔 중괄호 있어야 함
불필요한 toList():
// 전
...answers.toList().map((a) => Widget())
// 후 (spread 연산자는 Iterable을 직접 받음)
...answers.map((a) => Widget())
Null-safe 연산자 오용:
// non-nullable 변수에 ?. 쓰는 경우
final list = <String>[];
list?.map(...) // ⚠️ list is non-nullable
// 수정
list.map(...)
정리: 우선순위 접근법
flutter analyze --no-pub실행해서 전체 현황 파악- withOpacity 부터 - 수가 가장 많고 sed 한 줄로 해결됨
- warning 레벨 (미사용 import, 미사용 변수) - 파일별 수동 수정
- info 레벨 나머지 deprecation - 케이스별로 패턴 파악 후 수정
- 수정 후
flutter analyze --no-pub재실행으로 검증
한 번 정리해두면 이후엔 PR 머지 전 flutter analyze 결과를 확인하는 습관만으로 유지할 수 있다.

💬 댓글
비밀번호를 기억해두면 나중에 내 댓글을 삭제할 수 있어요.