기초

변수와 타입

Flutter 코드에서 값을 담는 방법을 var, final, const, nullable, State 변수로 나누어 이해합니다.

핵심 개념

Dart와 Flutter에서 자주 보는 변수 형태

명시 타입 변수

Stringintbool

String, int, double, bool처럼 타입을 직접 적는 방식입니다. 읽는 사람이 변수에 어떤 값이 들어가는지 바로 알 수 있습니다.

언제 쓰나: 모델 필드, 함수 반환값, 공개 API처럼 타입이 문서 역할을 해야 할 때 좋습니다.

Flutter에서: 위젯의 final 필드나 데이터 모델 클래스에서 가장 자주 봅니다.

// String 타입: 글자만 담을 수 있습니다.
String title = 'Flutter Wiki';

// int 타입: 정수만 담을 수 있습니다.
int completedCount = 3;

// bool 타입: true 또는 false만 담습니다.
bool isPublished = false;

var

var

초기값을 보고 Dart가 타입을 추론하게 하는 방식입니다. var라고 해서 아무 타입이나 들어가는 것은 아닙니다. 처음 String으로 추론되면 계속 String 변수입니다.

언제 쓰나: 지역 변수처럼 타입이 오른쪽 값만 봐도 너무 명확할 때 씁니다.

Flutter에서: build 메서드 안에서 임시 목록이나 계산값을 만들 때 자주 씁니다.

// 오른쪽 값이 문자열이므로 title은 String으로 추론됩니다.
var title = 'Dart 변수';

// 이 줄은 오류입니다. 이미 String으로 추론된 변수에 int를 넣을 수 없습니다.
// title = 10;

// 오른쪽 값이 리스트이므로 topics는 List<String>에 가깝게 추론됩니다.
var topics = ['변수', '함수', '클래스'];

final

final

한 번 값을 넣으면 다시 다른 값으로 바꿀 수 없는 변수입니다. 런타임에 계산된 값도 final에 담을 수 있습니다.

언제 쓰나: 값을 한 번 정한 뒤 바뀌면 안 되는 경우 기본 선택지로 삼으면 좋습니다.

Flutter에서: StatelessWidget의 생성자 값, 모델 객체의 필드, build 안의 계산 결과에 자주 사용합니다.

class LessonTitle extends StatelessWidget {
  const LessonTitle({super.key, required this.title});

  // 위젯이 받은 제목은 위젯 내부에서 바뀌지 않아야 하므로 final을 씁니다.
  final String title;

  @override
  Widget build(BuildContext context) {
    // Theme.of(context)는 런타임에 알 수 있지만 한 번 정한 뒤 바꾸지 않으므로 final이 적합합니다.
    final textTheme = Theme.of(context).textTheme;

    return Text(title, style: textTheme.titleLarge);
  }
}

const

const

컴파일 시점에 이미 값이 확정되는 상수입니다. final보다 더 강한 고정값이라고 생각하면 됩니다.

언제 쓰나: 숫자 상수, 고정 문자열, const 생성자가 있는 불변 객체를 만들 때 사용합니다.

Flutter에서: 변하지 않는 위젯 앞에 const를 붙이면 불필요한 객체 생성을 줄이고 코드 의도를 분명하게 합니다.

// 앱 전체에서 반복해서 쓰는 고정 여백입니다.
const double pagePadding = 16;

// Text 위젯도 const 생성자를 지원하므로 고정 문구라면 const를 붙일 수 있습니다.
const emptyMessage = Text('아직 문서가 없습니다.');

// 런타임 값은 const가 될 수 없습니다.
// const now = DateTime.now(); // 오류

late

late

선언 시점에는 값을 넣지 않지만, 사용하기 전에는 반드시 초기화하겠다고 약속하는 변수입니다.

언제 쓰나: initState에서 초기화해야 하는 컨트롤러처럼 생성 시점과 초기화 시점이 다른 경우에 씁니다.

Flutter에서: AnimationController, TabController, TextEditingController 같은 객체를 State에서 다룰 때 종종 등장합니다.

class _SearchPageState extends State<SearchPage> {
  // initState에서 초기화할 것이므로 late를 사용합니다.
  late final TextEditingController controller;

  @override
  void initState() {
    super.initState();
    controller = TextEditingController();
  }

  @override
  void dispose() {
    // 직접 만든 컨트롤러는 화면이 사라질 때 정리합니다.
    controller.dispose();
    super.dispose();
  }
}

nullable 변수

nullableString?

타입 뒤에 ?를 붙이면 null이 들어갈 수 있는 변수가 됩니다. 값이 없을 수 있다는 사실을 타입에 표시하는 방식입니다.

언제 쓰나: 선택 입력, 아직 로드되지 않은 데이터, 실패할 수 있는 검색 결과처럼 값이 없을 수 있을 때 사용합니다.

Flutter에서: API 응답 모델, 선택된 항목, 비동기 로딩 전 데이터에서 자주 쓰입니다.

String? selectedTopic;

// null이면 기본 문구를 사용합니다.
final label = selectedTopic ?? '선택된 주제가 없습니다.';

// null 검사를 통과한 뒤에는 안전하게 사용할 수 있습니다.
if (selectedTopic != null) {
  print(selectedTopic.length);
}

dynamic과 Object

dynamicObject

dynamic은 타입 검사를 미루는 변수이고, Object는 모든 값의 최상위 타입에 가깝습니다. 둘 다 남용하면 코드가 불안정해질 수 있습니다.

언제 쓰나: 외부 JSON처럼 타입을 아직 모르는 값을 잠깐 받을 때만 제한적으로 사용합니다.

Flutter에서: Map<String, dynamic> 형태의 JSON을 모델 클래스로 변환하는 중간 단계에서 자주 봅니다.

// 서버에서 온 원본 JSON은 dynamic 값을 포함할 수 있습니다.
final Map<String, dynamic> json = {
  'title': '변수와 타입',
  'minutes': 20,
};

// 가능한 한 빨리 명확한 타입으로 변환하는 것이 좋습니다.
final title = json['title'] as String;
final minutes = json['minutes'] as int;

State 안의 상태 변수

State 변수setState

StatefulWidget의 State 클래스 안에 선언되어 화면의 현재 상태를 기억하는 변수입니다.

언제 쓰나: 버튼 클릭 횟수, 입력값, 선택 탭처럼 사용자 행동에 따라 바뀌는 값을 저장할 때 사용합니다.

Flutter에서: setState와 함께 바뀌며, 값이 바뀌면 해당 화면이 다시 그려집니다.

class _CounterPageState extends State<CounterPage> {
  // 화면이 기억해야 하는 값입니다.
  int count = 0;

  void increase() {
    // 상태 변수 값을 바꾼 뒤 setState로 화면 갱신을 요청합니다.
    setState(() {
      count += 1;
    });
  }
}

상세 설명

더 알아보기

변수 선언은 단순히 값을 담는 문법이 아니라 코드의 의도를 드러내는 장치입니다. 값이 변하지 않는다는 사실을 final이나 const로 표현하면, 나중에 코드를 읽는 사람이 상태 변화 가능성을 훨씬 좁게 추적할 수 있습니다.

Flutter에서는 build 메서드가 자주 다시 실행될 수 있으므로 변하지 않는 값과 위젯을 const로 표현하는 습관이 성능과 가독성 모두에 도움이 됩니다.

코드

예제

// 강의 진행률을 표현하는 모델 클래스입니다.
class LessonProgress {
  // const 생성자는 같은 값으로 만들어지는 불변 객체에 적합합니다.
  const LessonProgress({
    required this.title,
    required this.completedCount,
    required this.totalCount,
  });

  // title은 글자만 담아야 하므로 String으로 명시합니다.
  final String title;

  // 완료 개수와 전체 개수는 정수이므로 int를 사용합니다.
  final int completedCount;
  final int totalCount;

  // get은 값을 저장하지 않고, 필요할 때 계산해서 돌려주는 읽기 전용 속성입니다.
  double get ratio => completedCount / totalCount;
}

void main() {
  // 컴파일 타임에 확정되는 고정값은 const로 선언합니다.
  const maxVisibleItems = 5;

  // 객체 생성 결과는 한 번 정해지면 바꾸지 않을 것이므로 final을 사용합니다.
  final progress = LessonProgress(
    title: 'Dart 변수와 타입',
    completedCount: 3,
    totalCount: 8,
  );

  // 오른쪽 값을 보고 Dart가 String으로 추론하므로 var를 사용할 수 있습니다.
  var message = '${progress.title}: ${progress.ratio * 100}%';

  // 변수에 담긴 값을 실제로 사용합니다.
  print(message);
  print(maxVisibleItems);
}

주의 사항

실무에서 확인할 점

  • 변할 수 없는 값은 기본적으로 final을 먼저 고려하세요.
  • const는 컴파일 타임 상수와 const 생성자가 있는 객체에 사용할 수 있습니다.
  • 공개 클래스의 필드는 명시 타입을 쓰는 편이 협업에서 더 읽기 쉽습니다.

다음 단계

실습 체크리스트

명시 타입과 var 구분final과 const 차이 설명State 안의 바뀌는 변수 이해
  • 출처세부 기준과 최신 변경 사항을 확인할 수 있습니다.
  • Flutter API Reference클래스, 메서드, 생성자 세부 정의를 확인합니다.
  • Dart languageDart 문법 자체가 궁금할 때 함께 봅니다.