guricode

[Flutter] 상수 클래스 본문

앱/Flutter&Dart

[Flutter] 상수 클래스

agentrakugaki 2025. 10. 20. 00:44

상수 클래스는 앱 전체에서 사용되는 고정된 값들을 한 곳에 모아놓은 클래스다. 색상, 폰트 크기, API URL, 문자열 등 변하지 않는 값들을 관리한다. 

 

주요 장점은:
1. 유지보수성 - 한 곳만 수정하면 전체 적용
2. 일관성 - 동일한 값 보장
3. 타입 안전성 - 컴파일 타임 오류 감지
4. 가독성 - 의미있는 이름 사용

실무에서는 AppColors, AppSizes, ApiConstants 등으로 
분류하여 관리하고, private 생성자로 인스턴스 
생성을 방지한다.

 

주요상수클래스

  • AppColors - 색상
  • AppTextStyles - 텍스트 스타일
  • AppSizes - 크기(padding, radius 등)
  • ApiConstants - API 관련
  • AppStrings - 문자열

 

 

색상 상수 AppColors

import 'package:flutter/material.dart';

class AppColors {
  // private 생성자 - 인스턴스 생성 방지
  AppColors._();
  
  // Primary Colors
  static const Color primary = Color(0xFF2196F3);
  static const Color primaryLight = Color(0xFF64B5F6);
  static const Color primaryDark = Color(0xFF1976D2);
  
  // Secondary Colors
  static const Color secondary = Color(0xFFFF9800);
  static const Color secondaryLight = Color(0xFFFFB74D);
  static const Color secondaryDark = Color(0xFFF57C00);
  
  // Neutral Colors
  static const Color black = Color(0xFF000000);
  static const Color white = Color(0xFFFFFFFF);
  static const Color grey = Color(0xFF9E9E9E);
  static const Color greyLight = Color(0xFFE0E0E0);
  static const Color greyDark = Color(0xFF616161);
  
  // Semantic Colors
  static const Color success = Color(0xFF4CAF50);
  static const Color warning = Color(0xFFFFC107);
  static const Color error = Color(0xFFF44336);
  static const Color info = Color(0xFF2196F3);
  
  // Background Colors
  static const Color background = Color(0xFFF5F5F5);
  static const Color surface = Color(0xFFFFFFFF);
  static const Color scaffoldBackground = Color(0xFFFAFAFA);
  
  // Text Colors
  static const Color textPrimary = Color(0xFF212121);
  static const Color textSecondary = Color(0xFF757575);
  static const Color textDisabled = Color(0xFFBDBDBD);
}

텍스트 스타일 상수 AppTextStyles

import 'package:flutter/material.dart';

class AppTextStyles {
  AppTextStyles._();
  
  // Headings
  static const TextStyle h1 = TextStyle(
    fontSize: 32,
    fontWeight: FontWeight.bold,
    color: AppColors.textPrimary,
  );
  
  static const TextStyle h2 = TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: AppColors.textPrimary,
  );
  
  static const TextStyle h3 = TextStyle(
    fontSize: 20,
    fontWeight: FontWeight.w600,
    color: AppColors.textPrimary,
  );
  
  // Body Text
  static const TextStyle bodyLarge = TextStyle(
    fontSize: 16,
    fontWeight: FontWeight.normal,
    color: AppColors.textPrimary,
  );
  
  static const TextStyle bodyMedium = TextStyle(
    fontSize: 14,
    fontWeight: FontWeight.normal,
    color: AppColors.textPrimary,
  );
  
  static const TextStyle bodySmall = TextStyle(
    fontSize: 12,
    fontWeight: FontWeight.normal,
    color: AppColors.textSecondary,
  );
  
  // Button Text
  static const TextStyle button = TextStyle(
    fontSize: 16,
    fontWeight: FontWeight.w600,
    color: AppColors.white,
  );
  
  // Caption
  static const TextStyle caption = TextStyle(
    fontSize: 12,
    fontWeight: FontWeight.normal,
    color: AppColors.textSecondary,
  );
}

크기 상수 AppSizes

class AppSizes {
  AppSizes._();
  
  // Padding & Margin
  static const double paddingXS = 4.0;
  static const double paddingS = 8.0;
  static const double paddingM = 16.0;
  static const double paddingL = 24.0;
  static const double paddingXL = 32.0;
  
  // Border Radius
  static const double radiusS = 4.0;
  static const double radiusM = 8.0;
  static const double radiusL = 16.0;
  static const double radiusXL = 24.0;
  static const double radiusRound = 999.0;
  
  // Icon Sizes
  static const double iconXS = 16.0;
  static const double iconS = 20.0;
  static const double iconM = 24.0;
  static const double iconL = 32.0;
  static const double iconXL = 48.0;
  
  // Button Heights
  static const double buttonHeightS = 36.0;
  static const double buttonHeightM = 48.0;
  static const double buttonHeightL = 56.0;
  
  // AppBar
  static const double appBarHeight = 56.0;
  
  // Bottom Navigation Bar
  static const double bottomNavBarHeight = 60.0;
}

API 상수 ApiConstants

class ApiConstants {
  ApiConstants._();
  
  // Base URLs
  static const String baseUrl = 'https://api.example.com';
  static const String baseUrlDev = 'https://dev-api.example.com';
  static const String baseUrlStaging = 'https://staging-api.example.com';
  
  // API Version
  static const String apiVersion = 'v1';
  
  // Endpoints
  static const String login = '/auth/login';
  static const String register = '/auth/register';
  static const String logout = '/auth/logout';
  static const String users = '/users';
  static const String posts = '/posts';
  static const String comments = '/comments';
  
  // Timeout
  static const int connectTimeout = 30000; // 30초
  static const int receiveTimeout = 30000; // 30초
  
  // Headers
  static const String headerContentType = 'Content-Type';
  static const String headerAuthorization = 'Authorization';
  static const String headerAccept = 'Accept';
  
  // Content Types
  static const String contentTypeJson = 'application/json';
  static const String contentTypeFormData = 'multipart/form-data';
}

문자열 상수 AppStrings

class AppStrings {
  AppStrings._();
  
  // App
  static const String appName = 'My Flutter App';
  static const String appVersion = '1.0.0';
  
  // Common
  static const String ok = '확인';
  static const String cancel = '취소';
  static const String save = '저장';
  static const String delete = '삭제';
  static const String edit = '수정';
  static const String loading = '로딩 중...';
  static const String error = '오류가 발생했습니다';
  static const String retry = '다시 시도';
  
  // Auth
  static const String login = '로그인';
  static const String logout = '로그아웃';
  static const String register = '회원가입';
  static const String email = '이메일';
  static const String password = '비밀번호';
  static const String forgotPassword = '비밀번호 찾기';
  
  // Validation
  static const String fieldRequired = '필수 입력 항목입니다';
  static const String invalidEmail = '올바른 이메일을 입력하세요';
  static const String passwordTooShort = '비밀번호는 8자 이상이어야 합니다';
  
  // Messages
  static const String loginSuccess = '로그인에 성공했습니다';
  static const String loginFailed = '로그인에 실패했습니다';
  static const String networkError = '네트워크 연결을 확인하세요';
}

이미지 경로 상수 AppImages

class AppImages {
  AppImages._();
  
  // Base Path
  static const String _basePath = 'assets/images';
  
  // Logo
  static const String logo = '$_basePath/logo.png';
  static const String logoWhite = '$_basePath/logo_white.png';
  
  // Icons
  static const String iconHome = '$_basePath/icons/home.png';
  static const String iconProfile = '$_basePath/icons/profile.png';
  static const String iconSettings = '$_basePath/icons/settings.png';
  
  // Illustrations
  static const String emptyState = '$_basePath/empty_state.png';
  static const String errorState = '$_basePath/error_state.png';
  static const String successState = '$_basePath/success_state.png';
  
  // Placeholder
  static const String userPlaceholder = '$_basePath/user_placeholder.png';
  static const String imagePlaceholder = '$_basePath/image_placeholder.png';
}

애니메이션 상수 AppAnimations

class AppAnimations {
  AppAnimations._();
  
  // Duration
  static const Duration fast = Duration(milliseconds: 150);
  static const Duration normal = Duration(milliseconds: 300);
  static const Duration slow = Duration(milliseconds: 500);
  
  // Curves
  static const Curve defaultCurve = Curves.easeInOut;
  static const Curve bounceCurve = Curves.bounceOut;
  static const Curve elasticCurve = Curves.elasticOut;
}

 


보통이런식으로 사용

lib/
├── constants/
│   ├── app_colors.dart
│   ├── app_text_styles.dart
│   ├── app_sizes.dart
│   ├── api_constants.dart
│   ├── app_strings.dart
│   ├── app_images.dart
│   └── app_animations.dart
├── screens/
├── widgets/
└── main.dart

const vs static const 차이

static const (권장)

class AppColors {
  static const Color primary = Color(0xFF2196F3);
  // 클래스 이름으로 접근: AppColors.primary
  // 인스턴스 생성 불필요
}

const (위젯에서)

const Text('Hello')  // 컴파일 타임 상수
Text('Hello')        // 런타임 생성

// const 사용 시 성능 향상
// Flutter가 위젯을 재사용함

 

1. Private 생성자 사용

class AppColors {
  // 인스턴스 생성 방지
  AppColors._();
  
  static const Color primary = Color(0xFF2196F3);
}

// 불가능
// var colors = AppColors();

// 가능
Color color = AppColors.primary;

2. 의미있는 네이밍

// 나쁜 예
static const Color c1 = Color(0xFF2196F3);
static const double s1 = 16.0;

// 좋은 예
static const Color primary = Color(0xFF2196F3);
static const double paddingMedium = 16.0;

3. 그룹화

class AppColors {
  AppColors._();
  
  // Primary 그룹
  static const Color primary = Color(0xFF2196F3);
  static const Color primaryLight = Color(0xFF64B5F6);
  static const Color primaryDark = Color(0xFF1976D2);
  
  // Secondary 그룹
  static const Color secondary = Color(0xFFFF9800);
  static const Color secondaryLight = Color(0xFFFFB74D);
  static const Color secondaryDark = Color(0xFFF57C00);
}

4. 환경별 분리 

// config/environment.dart
enum Environment { dev, staging, production }

class ApiConfig {
  static Environment currentEnv = Environment.dev;
  
  static String get baseUrl {
    switch (currentEnv) {
      case Environment.dev:
        return 'https://dev-api.example.com';
      case Environment.staging:
        return 'https://staging-api.example.com';
      case Environment.production:
        return 'https://api.example.com';
    }
  }
}

 


 

프로젝트를 진행하기전에 상수클래스를 정의하고 진행하는게 좋다.

유지보수성 한 곳에서 수정하면 전체 앱에 적용
일관성 동일한 값을 보장
가독성 의미있는 이름으로 코드 이해 쉬움
타입 안전성 컴파일 타임에 오류 감지
재사용성 여러 곳에서 동일한 값 사용
협업 팀원 간 통일된 규칙

주의사항

1. 너무 많은 상수 클래스 금지

// 과도한 분리
class ButtonColors { }
class TextColors { }
class BackgroundColors { }
// ... 너무 많음!

// 적절한 그룹화
class AppColors {
  // Button Colors
  static const Color buttonPrimary = ...;
  
  // Text Colors
  static const Color textPrimary = ...;
  
  // Background Colors
  static const Color background = ...;
}

2. 하드코딩과의 균형

// 한두 번만 쓰는 값은 상수로 만들 필요 없음
// 3번 이상 사용되면 상수로 추출 (Rule of Three)

3. 동적 값은 상수 불가

// 불가능
static const String currentTime = DateTime.now().toString();

// 가능
static String getCurrentTime() => DateTime.now().toString();

 


 

Const와 final의 차이점

  • const = 컴파일 타임 상수. 값이 빌드 시점에 확정. 객체도 불변이며 캐싱(동일 리터럴은 동일 인스턴스).
  • final = 런타임 1회 할당. 최초 한 번 할당 후 변경 불가. 값은 실행 중 계산 가능.

 

항목  const  final
결정 시점 컴파일 타임 런타임(첫 할당 시)
재할당 불가 불가
값 제약 반드시 상수식 아무 값이나 가능(단, 1회만)
객체 특성 깊은 불변 + canonicalization 참조 불변(내부는 가변 가능)
위젯 최적화 const 위젯 재사용 해당 없음
late 사용 불가 가능(late final)

예시:

const a = 3 * 7;          // OK, 컴파일 타임 상수
final b = DateTime.now(); // OK, 런타임에 결정

final list1 = [1,2];      // 리스트 재할당 불가, 내부 수정은 가능
list1.add(3);             // OK

const list2 = [1,2];      // 리스트와 요소 모두 불변
// list2.add(3);          // 컴파일 에러

class P { final int x; const P(this.x); }
const p1 = P(1), p2 = P(1);
identical(p1, p2); // true (동일 인스턴스)

요약:

  • const ⊃ final. const는 항상 final이지만, final은 상수일 필요 없음.
  • 변하지 않는 “값 자체”가 컴파일 시 확정이면 const. 실행 중 한 번 정해질 값이면 final.
  • 위젯 트리에서는 가능하면 const로 지정해 리빌드 비용을 줄인다.