1. Flutter 프로젝트 만들기
Visual Studio Code를 실행하고 명령어 팔레트를 엽니다(F1 또는 Ctrl+Shift+P 또는 Shift+Cmd+P 사용).
Flutter: New Project 명령어를 선택합니다.
Application을 선택하고 프로젝트를 만들 폴더를 선택합니다.
프로젝트의 이름을 지정합니다. (소문자, 스네이크)
경로 신뢰 체크합니다.
2. 초기 앱 복사 및 붙여넣기
VS Code의 왼쪽 창에서 Explorer가 선택되어 있는지 확인하고 pubspec.yaml 파일을 엽니다.
이 파일의 콘텐츠를 다음으로 바꿉니다. 이름은 기존의 것으로 유지하니다.
name: namer_app
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 0.0.1+1
environment:
sdk: '>=2.19.4 <4.0.0'
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
pubspec.yaml 파일은 앱에 관한 기본 정보(예: 현재 버전, 종속 항목, 함께 제공될 애셋)를 지정합니다.
프로젝트에서 또 다른 구성 파일 analysis_options.yaml을 엽니다.
파일의 콘텐츠를 다음으로 바꿉니다.
include: package:flutter_lints/flutter.yaml
linter:
rules:
prefer_const_constructors: false
prefer_final_fields: false
use_key_in_widget_constructors: false
prefer_const_literals_to_create_immutables: false
prefer_const_constructors_in_immutables: false
avoid_print: false
이 파일은 코드를 분석할 때 Flutter의 엄격성 정도를 결정합니다.
이번이 Flutter 첫 사용이므로 분석 도구는 느슨하게 설정한 것이고, 실제 프로덕션 앱 출시가 가까워지면 보다 엄격하게 조정해야 합니다.
이제 lib/ 디렉터리 아래의 main.dart 파일을 엽니다.
이 파일의 콘텐츠를 다음으로 바꿉니다.
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
return Scaffold(
body: Column(
children: [
Text('A random idea:'),
Text(appState.current.asLowerCase),
],
),
);
}
}
이 50줄의 코드가 지금까지 전체 앱입니다.
3. 버튼 추가
이 단계에서는 Next 버튼을 추가하여 새 단어 쌍을 생성합니다.
테스트 기기와 연결하기.
가상의 테스트 기기를 실행합니다.
Android Studio를 실행합니다.
Virtual Device Manager
미리 만들어둔 Divice를 실행합니다.
vscode 하단의 기기가 바뀌었습니다.
앱 실행
먼저 lib/main.dart를 열고 대상 기기가 선택되어 있는지 확인합니다. VS Code 오른쪽 하단에 현재 대상 기기를 보여주는 버튼이 있습니다. 클릭하여 변경합니다.
lib/main.dart가 열려 있는 동안 VS Code 오른쪽 상단에서 'play' 버튼을 찾아 클릭합니다.
1분 정도 지나면 앱이 디버그 모드로 실행됩니다.
아직은 간단한 앱입니다.
첫 번째 핫 리로드
lib/main.dart 하단에 텍스트를 추가하고 저장하면 바로 적용됩니다.
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
return Scaffold(
body: Column(
children: [
Text('A random idea:'),
Text(appState.current.asLowerCase),
Text('추가된 텍스트'),
],
),
);
}
}
Flutter에서 핫 리로드(Hot Reload)는
👉 코드를 수정하고 저장하면 앱을 다시 시작하지 않고, 변경사항을 즉시 UI에 반영해주는 기능입니다.
"화면만 새로 그려주고, 데이터는 그대로 둔다."
🔥 예시:
- 버튼 색을 바꿨다면 → 저장하자마자 화면에 반영됨
- 하지만 상태(state)는 그대로 유지됨 (예: 카운터 숫자, 현재 단어 등)
핫 리로드와 핫 리스타트의 차이
🔁 핫 리로드(Hot Reload) vs 🔄 핫 리스타트(Hot Restart)
구분 | 🔁 핫 리로드 | 🔄 핫 리스타트 |
속도 | 매우 빠름 (1~2초 이내) | 비교적 느림 (전체 리빌드) |
상태(state) 유지 | ✅ 유지됨 | ❌ 초기화됨 |
코드 반영 범위 | 주로 UI 변경 및 함수 내용 수정 | 모든 코드 변경 완전 반영 |
앱 재시작 여부 | ❌ 안 함 | ✅ 앱 완전 재시작 |
주로 언제 사용? | UI 디자인 조정, 간단한 로직 수정 시 | 상태 초기화하고 처음부터 실행해보고 싶을 때 |
📌 예시로 정리
작업 예시 | 핫 리로드 | 핫 리스타트 |
버튼 색 바꾸기 | ✅ | ✅ |
클래스 구조 변경 (새 필드 추가 등) | ⚠️ 불완전할 수 있음 | ✅ 완전 반영 |
상태 초기화해서 앱을 처음부터 보고 싶음 | ❌ | ✅ 필요 |
✅ 결론
- 디자인·UI 조정 중이라면 → 핫 리로드로 빠르게 반복
- 전체 로직 바꿨거나, 앱을 처음부터 새로 보고 싶다면 → 핫 리스타트
VS Code 기준:
- Hot Reload: 저장(Ctrl+S) 또는 "r"
- Hot Restart: "Shift + R" 또는 우측 상단 버튼 클릭
버튼 추가
lib/main.dart 하단에 Text 인스턴스 바로 아래 Column 하단에 버튼을 추가합니다.
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
return Scaffold(
body: Column(
children: [
Text('A random idea:'),
Text(appState.current.asLowerCase),
Text('추가된 텍스트'),
// ↓ 버튼을 위해 추가된 코드
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
],
),
);
}
}
버튼을 클릭하면 디버그콘솔에 button pressed! 메시지가 표시됩니다.
E/libEGL ( 5242): called unimplemented OpenGL ES API
D/EGL_emulation( 5242): app_time_stats: avg=52.44ms min=11.62ms max=1418.66ms count=39
I/flutter ( 5242): button pressed!
lib/main.dart 코드 구조 분석
앱의 구조, 상태 관리, 화면 구성 방식을 파악하기.
// 무작위 영어 단어쌍을 생성하는 패키지
import 'package:english_words/english_words.dart';
// Flutter의 기본 UI 구성요소를 가져옴
import 'package:flutter/material.dart';
// 상태 관리를 위한 Provider 패키지
import 'package:provider/provider.dart';
// 앱 실행의 진입점. Flutter는 runApp()으로 시작함
void main() {
runApp(MyApp()); // MyApp 위젯을 앱 전체로 사용함
}
// 앱의 기본 틀을 구성하는 위젯 (Stateless → 변경 불가능한 구조)
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
// MyAppState라는 상태 객체를 생성해서 아래 자식들에게 제공
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App', // 앱 이름
theme: ThemeData(
useMaterial3: true, // 최신 Material 디자인 적용
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange), // 앱 전체 색상 테마
),
home: MyHomePage(), // 앱 실행 시 처음 보여줄 화면 지정
),
);
}
}
// 상태를 담는 클래스. 단어쌍을 저장하고 변경될 때 알릴 수 있음
class MyAppState extends ChangeNotifier {
var current = WordPair.random(); // 현재 보여줄 무작위 단어쌍을 저장
}
// 메인 화면 위젯 (StatelessWidget이지만 상태는 Provider로부터 받음)
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// context를 통해 위에서 제공된 상태(MyAppState)를 가져옴
var appState = context.watch<MyAppState>();
return Scaffold(
// 화면 기본 구조 (상단바, 본문 등 포함 가능)
body: Column(
// 수직으로 위젯을 나열하는 레이아웃
children: [
Text('A random idea:'), // 설명 텍스트
Text(appState.current.asLowerCase), // 현재 단어쌍을 소문자로 보여줌
Text('추가된 텍스트'), // 예시용으로 텍스트 하나 더 추가
// ↓ 버튼 추가
ElevatedButton(
onPressed: () {
// 버튼을 누르면 콘솔에 로그 출력 (UI는 변화 없음)
print('button pressed!');
},
child: Text('Next'), // 버튼에 보일 텍스트
),
],
),
);
}
}
📦 import 구문
① english_words 패키지
import 'package:english_words/english_words.dart';
🔹 무작위 영어 단어쌍을 만들어주는 유틸리티 패키지입니다.
🔹 주로 예제 앱, 이름 생성기 등에 활용됩니다.
WordPair.random() // → "SilentTiger" 같은 단어쌍 생성
💡 이 패키지는 Pub.dev에 등록된 외부 패키지이며, pubspec.yaml 파일에 등록되어 있어야 사용할 수 있습니다.
② flutter/material.dart
import 'package:flutter/material.dart';
🔹 Flutter의 핵심 UI 라이브러리입니다.
🔹 Google의 Material Design 스타일 위젯을 제공합니다.
Scaffold, AppBar, ElevatedButton, Column, Text 등...
✅ Flutter 앱 대부분은 이 패키지를 기반으로 UI를 구성합니다.
Flutter 기본 템플릿에서도 항상 포함되는 필수 라이브러리입니다.
③ provider 패키지
import 'package:provider/provider.dart';
🔹 상태 관리를 도와주는 외부 라이브러리입니다.
🔹 화면에 보이는 UI와 내부 데이터(상태)를 연결해주는 역할을 합니다.
ChangeNotifierProvider, context.watch, context.read 등...
✅ 이 앱에서는 MyAppState라는 클래스를 전역으로 공유하기 위해 사용합니다.
✅ 정리 요약
패키지 이름 | 주요 기능 | 간단 설명 |
english_words | 무작위 단어쌍 생성 | 랜덤 텍스트 콘텐츠를 만들 때 사용 |
flutter/material.dart | 기본 UI 구성 | 버튼, 텍스트, 레이아웃 등 모든 UI 요소 제공 |
provider | 상태 관리 | 데이터 변경 시 UI 자동 갱신 |
🧩 전체 구조 미리 보기
- main(): 앱 실행의 시작점
- MyApp: 앱 전체의 뼈대
- MyAppState: 앱의 상태를 관리
- MyHomePage: 실제 화면 UI 구성
- Provider: 상태를 UI에 연결
- Hot Reload: 코드 변경 시 앱을 껐다 켜지 않고 바로 반영됨
파일 최상단에는 main() 함수가 있습니다. 현재 형식으로는 MyApp에서 정의된 앱을 실행하라고 Flutter에 지시합니다.
MyApp 클래스는 StatelessWidget을 확장합니다. 위젯은 모든 Flutter 앱을 빌드하는 데 사용되는 요소입니다. 앱 자체도 위젯인 것을 확인할 수 있습니다.
MyApp의 코드는 전체 앱을 설정합니다. 앱 전체 상태를 생성하고(나중에 자세히 설명) 앱의 이름을 지정하고 시각적 테마를 정의하고 '홈' 위젯(앱의 시작점)을 설정합니다.
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
MyAppState 클래스는 앱의 상태를 정의합니다. 이번이 처음으로 Flutter를 사용하는 것이므로 이 Codelab에서는 코드를 간단하고 명확하게 유지합니다. Flutter에는 앱 상태를 관리하는 강력한 방법이 여러 가지 있습니다. 설명하기 가장 쉬운 것 중 하나는 이 앱에서 사용하는 접근 방식인 ChangeNotifier입니다.
- MyAppState는 앱이 작동하는 데 필요한 데이터를 정의합니다. 지금은 현재 임의의 단어 쌍이 있는 단일 변수만 포함되어 있습니다. 나중에 더 추가합니다.
- 상태 클래스는 ChangeNotifier를 확장합니다. 즉, 자체 변경사항에 관해 다른 항목에 알릴 수 있습니다. 예를 들어 현재 단어 쌍이 변경되면 앱의 일부 위젯이 알아야 합니다.
- 상태가 만들어지고 ChangeNotifierProvider를 사용하여 전체 앱에 제공됩니다(위의 MyApp 코드 참고). 이렇게 하면 앱의 위젯이 상태를 알 수 있습니다.
https://codelabs.developers.google.com/codelabs/flutter-codelab-first?hl=ko#3
🖥 메인 화면 UI 구성
// 앱의 메인 화면
// 직접 상태를 가지지 않고, 외부 Provider에서 상태를 받아옴
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 위에서 Provider를 통해 공급된 MyAppState를 가져옴
var appState = context.watch<MyAppState>();
return Scaffold(
// 기본 화면 레이아웃 제공 (앱 바, 본문 등)
body: Column(
children: [
Text('A random AWESOME idea:'), // 설명 텍스트
Text(appState.current.asLowerCase), // 소문자로 된 단어쌍 출력
ElevatedButton(
onPressed: () {
print('button pressed!'); // 버튼 클릭 시 로그 출력
},
child: Text('Next'), // 버튼에 표시될 텍스트
),
],
),
);
}
}
🔄 Flutter의 상태 관리 핵심: Provider와 ChangeNotifier
- MyAppState는 앱의 상태를 담당하며 ChangeNotifier를 상속합니다.
- 이 말은 "내 상태가 바뀌었어!" 하고 다른 위젯에게 알려줄 수 있다는 뜻입니다.
- Provider는 이 상태를 앱 전체에 공유해주는 역할을 합니다.
- context.watch<MyAppState>()를 통해 현재 상태를 가져오고, 상태가 변경되면 해당 위젯은 자동으로 다시 그려집니다.
⚡️ Hot Reload란?
- 코드를 저장하면 앱이 껐다 켜지지 않고 즉시 변경사항을 UI에 반영함.
- 단, WordPair.random() 같은 값은 상태를 직접 바꾸지 않으면 그대로 유지됩니다.
- → 앱은 새로 빌드되지만 current 값은 변하지 않음.
🧠 StatelessWidget vs StatefulWidget
Flutter에서는 모든 화면 요소가 Widget입니다. 그중 핵심은 두 가지:
Flutter의 모든 UI는 Widget으로 구성되며, 그 중 가장 핵심적인 두 가지가 StatelessWidget, StatefulWidget이며,
이 둘의 차이는 “상태(state)의 변화 유무”에 있습니다.
🔍 차이 요약표
구분 | StatelessWidget | StatefulWidget |
상태 변경 가능? | ❌ 불가능 (한 번 생성되면 그대로 유지) | ✅ 가능 (setState()로 UI 갱신 가능) |
사용 목적 | 단순 UI, 고정된 내용 | 사용자 인터랙션, 데이터 변경이 있는 UI |
예시 | 아이콘, 정적 텍스트, 로고 등 | 버튼 클릭, 입력폼, 애니메이션, 타이머 등 |
코드 구조 | 클래스 하나 | 클래스 두 개 (StatefulWidget + State) |
UI 다시 그림? | 새로 build() 호출 안 함 | 상태 바뀌면 build() 다시 호출됨 |
📌 StatelessWidget
- 상태를 가지지 않음
- 값이 변하지 않는 단순한 UI에 적합
class MyLabel extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('고정된 텍스트');
}
}
🔁 StatefulWidget
- 내부에 상태가 있고, 값이 바뀔 때마다 UI를 다시 그림
class Counter extends StatefulWidget {
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () {
setState(() {
count++; // 상태 변경
});
},
child: Text('증가'),
),
],
);
}
}
🧾 요약 정리
상황 | 추천 위젯 |
값이 고정되어 변화 없음 | StatelessWidget |
값이 바뀌고 그에 따라 화면도 바뀜 | StatefulWidget 또는 상태 관리 + StatelessWidget |
버튼을 상태에 연결하기
🧩 1단계: 상태 클래스에 기능 추가하기
우선 MyAppState 클래스에 새로운 단어쌍을 생성하는 메서드를 하나 추가해줄게요.
📍 lib/main.dart 파일의 MyAppState 클래스 아래에 다음 코드를 추가해 주세요:
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
// 새로운 단어쌍을 생성하고, 화면에 알리는 메서드
void getNext() {
current = WordPair.random();
notifyListeners(); // 상태가 바뀌었다고 구독자(화면)에게 알림
}
}
🔍 이게 무슨 뜻인가요?
- getNext()는 새로운 WordPair를 만들어 current 변수에 저장합니다.
- 그다음 notifyListeners()를 호출해서, 화면(UI)에 변경이 생겼다고 알립니다.
- 이 메서드는 ChangeNotifier에서 제공하는 기능이에요.
🧩 2단계: 버튼에 기능 연결하기
이제 버튼을 누르면 getNext()가 실행되도록 연결해볼게요.
이전에는 print()만 했던 부분을 다음처럼 바꿔주세요.
📍 lib/main.dart에서 ElevatedButton 부분을 다음과 같이 수정:
ElevatedButton(
onPressed: () {
appState.getNext(); // 상태를 변경하는 메서드 호출
},
child: Text('Next'),
)
여기에서 만들어진 단어가 버튼을 클릭할 때 마다 달라집니다.
Text(appState.current.asLowerCase),
'Flutter + Dart > Flutter + Dart 공부' 카테고리의 다른 글
day1 Flutter 실습: 버튼으로 리스트뷰 정렬 및 새로고침 구현하기 (0) | 2025.05.07 |
---|---|
앱 UI 수정 - 위젯 추출, 컬럼 중앙 배열 (0) | 2025.05.06 |
Flutter 위젯 기본 이해 (1) | 2025.05.05 |
Windows에서 Flutter 개발환경 구축하기 (0) | 2025.05.03 |
Dart 개요 (1) | 2025.05.03 |