f5a18a52f4
- CLAUDE.md: 모노레포 개요·표준 응답 포맷·에러 코드 맵(구현 일치)·환경변수 위임 구조(SSOT) 정리 - nestjs/vue3/flutter 규칙을 실제 구현(클래스명, env키, 디렉터리)과 정합화 - 루트 .env 단일 진실 공급원 구조에 맞춰 docker-compose 변수명 정정(BACKEND_API_URL, DB/REDIS_DATA_DIR) - backend/frontend/flutter 예제 env 추가(루트 위임 반영, 서비스 고유 값만 작성) - README.md 신규 작성, command.md 범용화, .gitignore에 *.example 재포함 규칙 추가 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.2 KiB
5.2 KiB
description
| description |
|---|
| Flutter 모바일 앱 개발 규칙 |
이 파일은
.claude/commands/flutter.md에 위치한다. 사용법:/flutter [요청 내용]
언어 & 기본 원칙
- Dart Sound Null Safety 완벽 준수 (
?,!,late올바르게 사용) dynamic타입 사용 지양 → 명시적 타입 선언- 모든 주석은 한국어로 작성
프로젝트 구조
lib/
├── main.dart
├── app.dart ← MaterialApp / 라우터 설정
├── core/
│ ├── constants/ ← 상수 (색상, 크기, 텍스트 등)
│ ├── theme/ ← 앱 테마 정의
│ ├── api/ ← Dio 인스턴스(dio_client), 에러 사전(error_lexicon), 인터셉터
│ ├── storage/ ← flutter_secure_storage 래퍼 (선택)
│ └── utils/ ← 순수 유틸 함수
├── features/
│ └── user/
│ ├── data/
│ │ ├── models/ ← JSON 직렬화 모델
│ │ └── repositories/ ← API 호출 구현체
│ ├── domain/
│ │ └── entities/ ← 순수 비즈니스 엔티티
│ └── presentation/
│ ├── pages/ ← 화면 단위 위젯
│ ├── widgets/ ← 재사용 위젯
│ └── providers/ ← 상태 관리
└── shared/
└── widgets/ ← 앱 공통 위젯
위젯 작성 원칙
// ✅ 좋은 예 — 작은 위젯으로 분리
class UserProfilePage extends StatelessWidget {
const UserProfilePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const _AppBar(),
body: const _ProfileBody(),
);
}
}
class _ProfileBody extends StatelessWidget {
const _ProfileBody();
@override
Widget build(BuildContext context) {
return Column(
children: [
const _AvatarSection(),
const _InfoSection(),
],
);
}
}
- 위젯 트리 depth가 깊어지면 커스텀 위젯으로 분리
build()메서드 내 비즈니스 로직 작성 금지const생성자 적극 활용 (불필요한 리빌드 방지)
반응형 UI
// ❌ 하드코딩 금지
Container(width: 375, height: 200)
// ✅ 반응형 처리
Container(
width: MediaQuery.of(context).size.width * 0.9,
child: ...,
)
// ✅ 유연한 레이아웃
Expanded(flex: 2, child: LeftPanel()),
Expanded(flex: 1, child: RightPanel()),
Expanded,Flexible,MediaQuery,LayoutBuilder적극 활용- 하드코딩 픽셀값 사용 금지
- 폰트 크기는
Theme.of(context).textTheme활용
상태 관리
// Riverpod 예시 (권장)
@riverpod
Future<User> fetchUser(FetchUserRef ref, int id) async {
final repo = ref.watch(userRepositoryProvider);
return repo.getUser(id);
}
// 위젯에서 사용
class UserWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(fetchUserProvider(userId));
return userAsync.when(
data: (user) => Text(user.name),
loading: () => const CircularProgressIndicator(),
error: (e, _) => const ErrorWidget(),
);
}
}
- Riverpod 권장 (Provider는 레거시로 간주)
StatefulWidget은 애니메이션, 폼 등 로컬 UI 상태에만 제한적 사용
API 통신 (Dio + 인터셉터)
// core/api/dio_client.dart
class DioClient {
late final Dio _dio;
DioClient() {
// flutter_dotenv 로 .env 로드, 미설정 시 로컬 기본값
final baseUrl = dotenv.maybeGet('API_URL') ?? 'http://localhost:3000/api';
_dio = Dio(BaseOptions(baseUrl: baseUrl));
_dio.interceptors.add(AuthInterceptor()); // 토큰 자동 첨부
_dio.interceptors.add(ErrorInterceptor()); // 에러 코드 처리 (error_lexicon 참조)
}
}
// 에러 인터셉터 — CLAUDE.md 에러 맵 기준 한국어 메시지 처리
class ErrorInterceptor extends Interceptor {
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
// 에러 코드 파싱 후 SnackBar/Dialog 표시
}
}
보안 (토큰 저장)
// flutter_secure_storage 사용
const storage = FlutterSecureStorage();
// 저장 (키 이름은 dio_client 인터셉터와 통일: jwt_token)
await storage.write(key: 'jwt_token', value: token);
// 읽기
final token = await storage.read(key: 'jwt_token');
// 삭제 (로그아웃)
await storage.deleteAll();
- JWT, 민감 정보는 반드시
flutter_secure_storage사용 SharedPreferences에 토큰 저장 금지
성능 & 최신 트렌드 추가 규칙
- 모델 직렬화:
json_serializable+freezed사용 권장 (불변 모델) - 이미지:
cached_network_image로 네트워크 이미지 캐싱 - 목록: 긴 목록은
ListView.builder사용 (전체 렌더링 금지) - 에러 핸들링:
Either<Failure, Success>패턴 고려 (fpdart패키지) - 라우팅:
go_router사용 권장 (딥링크, 중첩 라우팅 지원) - 환경 분리:
--dart-define또는flutter_dotenv로 dev/prod 분리