지난 수업에서 첫 Flutter 프로젝트를 만들었습니다. 프로젝트가 매우 간단한데 버튼 하나만 있고 버튼을 클릭하면 숫자가 증가하는 기능입니다. 아래는 이 프로젝트의 디렉터리 구조입니다.
프로젝트 디렉터리

android, web, windows, lib, test 등 많은 폴더와 파일이 보일 겁니다. 그렇다면 우리가 작성할 소스 코드는 어디에 있을까요? android? windows? 사실 그곳이 아니라 lib 폴더 안에 있습니다. lib를 열어보면 main.dart 파일이 보입니다.

이 lib 폴더는 앞으로 모든 소스 코드를 저장하는 곳입니다. 작성하는 언어는 Dart이며 Java나 C++가 아닙니다. Flutter는 Dart 언어를 사용합니다. 그런데 왜 프로젝트 루트에 android, web, windows 같은 디렉터리가 존재할까요?
이해하기 쉽게 설명하면 Flutter가 여러 플랫폼용 앱으로 컴파일될 수 있는 건 이 폴더들 덕분입니다.
Flutter는 단 한 세트의 Dart 코드로 다중 플랫폼에서 실행하는 프레임워크입니다. lib 폴더는 직접 작성하는 플랫폼 공통 비즈니스 코드를 담고 있습니다. 반면 android / windows / web / macos / linux 폴더들은 각 플랫폼 네이티브 호스트 프로젝트로, Flutter 엔진을 해당 시스템에 내장해 실행하는 역할을 수행합니다.
1 android 폴더
안드로이드 플랫폼 네이티브 프로젝트입니다.
안드로이드 앱 실행 컨테이너를 제공하고 Flutter 렌더링 엔진을 탑재합니다. 안드로이드 관련 하위 설정은 모두 여기서 관리합니다. 예를 들어 앱 권한 설정, 아이콘, 패키지 이름 등을 수정할 수 있습니다. 즉 안드로이드 고유 기능을 설정하거나 카메라, 블루투스, 알림, 위치 등 안드로이드 네이티브 API를 호출할 수 있습니다.
2 windows 폴더
Windows 데스크톱 네이티브 프로젝트입니다.
C++ Win32 네이티브 프로젝트로 Windows 시스템 창을 생성하고 Skia 렌더링 엔진을 초기화합니다. Skia는 구글 그래픽 라이브러리로 Flutter 데스크톱 앱의 창과 각종 컨트롤을 렌더링하는 데 사용됩니다.
windows 디렉터리 안에는 Windows 데스크톱 소프트웨어 하위 설정이 담겨있습니다. 창 크기, 창 제목 표시줄, 아이콘, 시스템 트레이(Windows 오른쪽 하단 아이콘) 등을 설정할 수 있습니다.
최종적으로 exe, msix 형식으로 패키징해 마이크로소프트 스토어에 배포할 수 있습니다.
3. web 폴더
정적 웹 페이지 프로젝트(HTML/JS/CSS)로 웹 진입점 파일 index.html이 제공됩니다. 작성한 Dart 소스는 최종 웹 리소스로 컴파일됩니다.
웹 페이지 제목, 아이콘, 정적 리소스, 라우팅을 설정할 수 있으며 컴파일하면 HTML/CSS/JS 파일이 출력되어 서버에 업로드한 뒤 브라우저로 열 수 있습니다.
기타 폴더
프로젝트 생성 당시 ios, macos, Linux 등 옵션을 체크하지 않았기 때문에 이 폴더들은 없지만 원리는 모두 동일합니다.
ios:애플 iOS 네이티브 Xcode 프로젝트, ipa 파일로 패키징해 아이폰에 출시
macos:macOS 데스크톱 코코아 프로젝트, 맥 컴퓨터 클라이언트용
linux:Linux 데스크톱 GTK 프로젝트
안드로이드 스튜디오 오른쪽 상단에서 플랫폼을 선택하면 각 버전의 앱을 컴파일할 수 있습니다. 명령줄로 직접 실행하는 것도 가능하며, 안드로이드 스튜디오에서 플랫폼을 고르고 초록색 삼각형 실행 버튼을 누르는 행위는 내부적으로 Flutter 명령어를 실행하는 것과 같습니다.
# 안드로이드 실행, android 디렉터리 설정 참고
flutter run -d android
# Windows 데스크톱 실행, windows 내부 C++ 프로젝트 참고
flutter run -d windows
# 웹 실행, web 디렉터리 html 파일 참고
flutter run -d chromeCode language: PHP (php)

windows 폴더 안의 runner / main.cpp 파일을 열어보세요
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include "flutter_window.h"
#include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) {
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
CreateAndAttachConsole();
}
// Initialize COM, so that it is available for use in the library and/or
// plugins.
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
flutter::DartProject project(L"data");
std::vector<std::string> command_line_arguments =
GetCommandLineArguments();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.Create(L"myproject", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);
::MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
::CoUninitialize();
return EXIT_SUCCESS;
}
Code language: PHP (php)
Windows 데스크톱으로 빌드할 경우 이 main.cpp가 진짜 프로그램 진입점입니다. 먼저 이 C++ 파일이 실행된 뒤 lib/main.dart로 제어가 넘어갑니다.
마찬가지로 android 폴더를 열어보면 코틀린이나 자바로 개발하는 일반 안드로이드 앱과 거의 동일한 구조를 볼 수 있습니다.

src 폴더, AndroidManifest.xml 파일, MainActivity 등이 모두 존재해 안드로이드 개발 경험이 있다면 익숙할 겁니다. 사실 Flutter로 안드로이드 앱을 개발할 때 실행 흐름은 이 android 디렉터리부터 시작합니다.
마찬가지로 web 폴더를 열면 웹 진입점 파일인 index.html을 확인할 수 있습니다.

dart 파일 설명
lib 아래의 main.dart가 앞으로 개발의 핵심 파일입니다. lib 안에 하위 폴더를 만들고 다른 dart 파일을 추가할 수도 있습니다.
main.dart를 열어보겠습니다
// Material 컴포넌트 라이브러리 불러오기
import 'package:flutter/material.dart';
// 프로그램 진입 함수
void main() {
runApp(const MyApp());
}
// 앱 루트 컴포넌트 (무상태 위젯)
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
// 전역 테마 설정
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
// 기본 메인 페이지
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
// 메인 페이지 (상태 위젯)
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// 페이지 제목 매개변수
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
// 메인 페이지 상태 관리 클래스
class _MyHomePageState extends State<MyHomePage> {
// 카운터 변수
int _counter = 0;
// 카운터 증가 메서드
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
// 페이지 기본 레이아웃 틀
return Scaffold(
// 상단 앱바
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
// 페이지 본문 영역
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Text(
'$_counter', // 숫자 변수 표시
style: Theme.of(context).textTheme.headlineMedium, // 텍스트 스타일
),
],
),
),
// 우측 하단 플로팅 버튼: onPressed 클릭 시 _incrementCounter 실행, tooltip 툴팁 텍스트, child 아이콘 지정
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}Code language: JavaScript (javascript)
1. 진입 부분
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
Code language: JavaScript (javascript)
- 구글 공식 Material UI 컴포넌트 묶음(버튼, 페이지, 텍스트 등)을 임포트합니다. 애플은 자체 UI 프레임워크를 따로 제공합니다.
main는 Dart 프로그램 유일한 진입점입니다.runApp()로 앱을 실행하고 루트 컴포넌트MyApp을 연결합니다.
2. MyApp 전역 루트 위젯
StatelessWidget 즉 무상태 위젯입니다. 자세한 설명은 뒤에서 다루므로 지금은 간략히 알아두기만 하세요.
class MyApp extends StatelessWidget {
const MyApp({super.key});
// 앱 최상단 컴포넌트
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
Code language: JavaScript (javascript)
- 전역 설정:앱 이름, 전체 테마, 기본 시작 페이지
MaterialApp:머티리얼 디자인 앱 외곽으로 라우팅, 테마, 제목을 통합 관리home로 앱 실행 시 기본 페이지인MyHomePage를 지정
3. MyHomePage 메인 페이지
창이나 앱을 열면 기본으로 보이는 페이지로 StatefulWidget 즉 상태 위젯입니다. 자세한 내용은 후술합니다.
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}Code language: JavaScript (javascript)
- 카운터 숫자 같은 가변 데이터를 담고 있으므로 상태 위젯을 사용합니다.
- 외부에서 제목
title을 전달받고 상태 클래스 안에서widget.title로 읽어옵니다. - 반드시
_MyHomePageState클래스를 짝으로 만들어 페이지 변수와 상호작용 로직을 보관합니다.
4. _MyHomePageState
상태 로직 클래스로 페이지 동작, 렌더링, 갱신 관련 처리를 담당합니다.
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}Code language: JavaScript (javascript)
int _counter = 0:페이지 가변 데이터인 카운터 값_incrementCounter():버튼 클릭 이벤트setState():Flutter에 데이터 변경을 알리고 UI를 새로 갱신하도록 요청- 카운터를 1 증가
build():화면 갱신할 때마다 실행되며 전체 페이지 레이아웃을 반환
5. Scaffold 페이지 틀
페이지 레이아웃 템플릿으로 _MyHomePageState 클래스 내부에 작성합니다.
appBar:상단 제목 바body:페이지 본문 영역Center:요소 가운데 정렬,Column:텍스트 수직 배치
floatingActionButton:우측 하단 원형 더하기 버튼, 클릭 시 증가 메서드 연결
전체 실행 흐름
- 프로그램 실행 →
main함수 실행 →MyApp로드 MyApp가MaterialApp렌더링 → 기본 페이지MyHomePage로드- 화면에 제목바, 중앙 텍스트, 더하기 버튼이 표시됨
- 버튼 클릭 →
_incrementCounter실행 +setState로 화면 재렌더링 → 숫자 갱신
첫 Flutter 프로젝트 이해하기