본문 바로가기
Flutter + Dart/Flutter + Dart 공부

프로젝트 만들기

by GREEN나무 2025. 5. 6.
728x90

개발환경 구축 

프로젝트 생성 

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),