Flutter 튜토리얼 61편: 데스크톱 앱 개발
개요
Flutter로 Windows, macOS, Linux 세 가지 데스크톱 플랫폼용 앱을 개발하는 방법을 알아봅니다. 각 플랫폼의 특성, 네이티브 통합, 빌드 및 배포 준비까지 포괄적으로 다룹니다.
graph TB
subgraph "Flutter 데스크톱 플랫폼"
A[Flutter] --> B[Windows]
A --> C[macOS]
A --> D[Linux]
end
subgraph "네이티브 통합"
B --> E[Win32 API / COM]
C --> F[Cocoa / Swift]
D --> G[GTK / C]
end
1. 데스크톱 개발 환경 설정
Why: 왜 데스크톱 앱을 개발하나요?
데스크톱 앱은 모바일 앱보다 더 많은 시스템 리소스와 기능에 접근할 수 있습니다. 파일 시스템 접근, 멀티 윈도우, 시스템 트레이 등 데스크톱 특화 기능을 활용할 수 있습니다.
What: 플랫폼별 요구사항
| 플랫폼 | 개발 환경 | 필수 도구 |
|---|---|---|
| Windows | Windows 10+ | Visual Studio 2022, CMake |
| macOS | macOS 10.14+ | Xcode, CocoaPods |
| Linux | Ubuntu 18.04+ | Clang, CMake, GTK 3, pkg-config |
How: 환경 설정 방법
Windows 개발 환경
# Visual Studio 설치 확인flutter doctor
# Windows 데스크톱 활성화 (기본 활성화됨)flutter config --enable-windows-desktop
# 프로젝트에 Windows 플랫폼 추가flutter create --platforms=windows .macOS 개발 환경
# Xcode 설치 확인xcode-select --install
# macOS 데스크톱 활성화flutter config --enable-macos-desktop
# 프로젝트에 macOS 플랫폼 추가flutter create --platforms=macos .Linux 개발 환경
# 필수 패키지 설치 (Ubuntu/Debian)sudo apt-get updatesudo apt-get install clang cmake ninja-build pkg-config \ libgtk-3-dev liblzma-dev libstdc++-12-dev
# Linux 데스크톱 활성화flutter config --enable-linux-desktop
# 프로젝트에 Linux 플랫폼 추가flutter create --platforms=linux .2. Windows 앱 개발
Why: 왜 Windows 특화 기능이 필요한가요?
Windows는 전 세계에서 가장 많이 사용되는 데스크톱 OS입니다. Windows 사용자에게 익숙한 UI와 기능을 제공하면 사용자 경험이 향상됩니다.
What: Windows 통합 기능
- Win32 API: 시스템 수준 기능 접근
- COM 인터페이스: Windows 컴포넌트 활용
- Fluent Design: Windows 디자인 가이드라인
How: Windows 앱 구현
dart 통한 네이티브 통합
import 'dart:ffi';import 'package:ffi/ffi.dart';
// Win32 API 호출 예제typedef MessageBoxNative = Int32 Function( IntPtr hWnd, Pointer<Utf16> lpText, Pointer<Utf16> lpCaption, Uint32 uType,);typedef MessageBoxDart = int Function( int hWnd, Pointer<Utf16> lpText, Pointer<Utf16> lpCaption, int uType,);
void showWindowsMessageBox(String title, String message) { final user32 = DynamicLibrary.open('user32.dll'); final messageBox = user32 .lookupFunction<MessageBoxNative, MessageBoxDart>('MessageBoxW');
final titlePointer = title.toNativeUtf16(); final messagePointer = message.toNativeUtf16();
messageBox(0, messagePointer, titlePointer, 0);
calloc.free(titlePointer); calloc.free(messagePointer);}win32 패키지 활용
dependencies: win32: ^5.0.0import 'package:win32/win32.dart';
void showNotification() { // Windows 토스트 알림, 레지스트리 접근 등 // win32 패키지가 제공하는 간편한 API 사용}Fluent Design UI 적용
dependencies: fluent_ui: ^4.0.0import 'package:fluent_ui/fluent_ui.dart';
class WindowsApp extends StatelessWidget { @override Widget build(BuildContext context) { return FluentApp( title: 'Windows App', theme: FluentThemeData.light(), darkTheme: FluentThemeData.dark(), home: NavigationView( appBar: NavigationAppBar( title: Text('Windows Style App'), ), pane: NavigationPane( selected: 0, items: [ PaneItem( icon: Icon(FluentIcons.home), title: Text('Home'), body: Center(child: Text('홈 화면')), ), PaneItem( icon: Icon(FluentIcons.settings), title: Text('Settings'), body: Center(child: Text('설정 화면')), ), ], ), ), ); }}호스트 애플리케이션 커스터마이징
Win32Window::Point origin(10, 10);Win32Window::Size size(1280, 720); // 창 크기 조정if (!window.CreateAndShow(L"My App", origin, size)) { return EXIT_FAILURE;}Watch out: Windows 주의사항
- Visual C++ 재배포 가능 패키지: 배포 시 함께 포함 필요
- 앱 아이콘:
windows/runner/resources/app_icon.ico교체 - 실행 파일명:
windows/CMakeLists.txt의BINARY_NAME수정
3. macOS 앱 개발
Why: 왜 macOS 특화 기능이 필요한가요?
macOS 사용자는 일관된 Apple 경험을 기대합니다. App Store 배포와 시스템 통합을 위해 macOS 특화 설정이 필요합니다.
What: macOS 통합 기능
How: macOS 앱 구현
Cupertino 스타일 UI
import 'package:flutter/cupertino.dart';
class MacOSApp extends StatelessWidget { @override Widget build(BuildContext context) { return CupertinoApp( title: 'macOS App', theme: CupertinoThemeData( primaryColor: CupertinoColors.systemBlue, ), home: CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( middle: Text('macOS Style App'), ), child: SafeArea( child: Center( child: CupertinoButton.filled( child: Text('버튼'), onPressed: () {}, ), ), ), ), ); }}macos_ui 패키지 활용
dependencies: macos_ui: ^2.0.0import 'package:macos_ui/macos_ui.dart';
class MacOSNativeApp extends StatelessWidget { @override Widget build(BuildContext context) { return MacosApp( title: 'macOS Native App', theme: MacosThemeData.light(), darkTheme: MacosThemeData.dark(), home: MacosWindow( sidebar: Sidebar( minWidth: 200, builder: (context, scrollController) { return SidebarItems( currentIndex: 0, items: [ SidebarItem( leading: MacosIcon(CupertinoIcons.home), label: Text('Home'), ), SidebarItem( leading: MacosIcon(CupertinoIcons.settings), label: Text('Settings'), ), ], onChanged: (index) {}, ); }, ), child: Center(child: Text('Content')), ), ); }}Entitlements 설정
<!-- macos/Runner/Release.entitlements --><?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <!-- 앱 샌드박스 활성화 --> <key>com.apple.security.app-sandbox</key> <true/>
<!-- 네트워크 클라이언트 접근 --> <key>com.apple.security.network.client</key> <true/>
<!-- 사용자 선택 파일 읽기 --> <key>com.apple.security.files.user-selected.read-only</key> <true/>
<!-- 카메라 접근 --> <key>com.apple.security.device.camera</key> <true/></dict></plist>macOS Entitlements 설정 가이드
graph TB
subgraph "Debug/Profile 모드"
A[DebugProfile.entitlements] --> B[JIT 허용]
A --> C[네트워크 서버 허용]
end
subgraph "Release 모드"
D[Release.entitlements] --> E[App Sandbox]
D --> F[필요한 권한만]
end
| Entitlement | 용도 |
|---|---|
com.apple.security.network.client | 외부 네트워크 요청 |
com.apple.security.network.server | 들어오는 연결 수신 |
com.apple.security.files.user-selected.read-only | 사용자 선택 파일 읽기 |
com.apple.security.files.user-selected.read-write | 사용자 선택 파일 읽기/쓰기 |
com.apple.security.device.camera | 카메라 접근 |
com.apple.security.device.microphone | 마이크 접근 |
Watch out: macOS 주의사항
- 두 개의 Entitlements 파일: Debug용과 Release용을 별도로 관리
- App Sandbox: App Store 배포 시 필수
- Hardened Runtime: 공증(Notarization) 시 필수
- 서명: 배포 시 유효한 개발자 인증서 필요
4. Linux 앱 개발
Why: 왜 Linux를 지원하나요?
Linux는 개발자와 기업 환경에서 널리 사용됩니다. 오픈소스 생태계와 잘 어울리며, 서버 환경에서의 GUI 도구 개발에 유용합니다.
What: Linux 통합 기능
- dart
: C 라이브러리 호출 - GTK4: Linux 네이티브 UI 프레임워크
- Canonical 패키지: Ubuntu/Linux 특화 기능
How: Linux 앱 구현
dart 통한 C 라이브러리 호출
import 'dart:ffi';
// C 함수 시그니처 정의typedef NativeAdd = Int32 Function(Int32 a, Int32 b);typedef DartAdd = int Function(int a, int b);
int callNativeAdd(int a, int b) { // 공유 라이브러리 로드 final dylib = DynamicLibrary.open('libmylib.so');
// 함수 룩업 final add = dylib.lookupFunction<NativeAdd, DartAdd>('add');
return add(a, b);}Canonical 패키지 활용
dependencies: # Ubuntu 데스크톱 알림 desktop_notifications: ^0.6.0 # D-Bus 통신 dbus: ^0.7.0 # 네트워크 관리 nm: ^0.5.0import 'package:desktop_notifications/desktop_notifications.dart';
Future<void> showLinuxNotification() async { final client = NotificationsClient(); await client.notify( 'Flutter 앱', body: '알림 내용입니다.', expireTimeout: Duration(seconds: 5), ); await client.close();}Linux 빌드 출력 구조
build/linux/x64/release/bundle/├── my_app # 실행 파일├── lib/│ ├── libflutter_linux_gtk.so│ └── ... # 기타 .so 라이브러리└── data/ ├── flutter_assets/ # Flutter 에셋 └── icudtl.dat # ICU 데이터Watch out: Linux 주의사항
- 라이브러리 의존성:
ldd명령으로 확인 - GTK 버전: 대상 시스템의 GTK 3 버전 확인
- 배포 형식: AppImage, Flatpak, Snap 등 선택
5. 플랫폼별 빌드 명령
빌드 및 실행
# 개발 모드 실행flutter run -d windowsflutter run -d macosflutter run -d linux
# 릴리스 빌드flutter build windows --releaseflutter build macos --releaseflutter build linux --release빌드 결과물 위치
| 플랫폼 | 경로 |
|---|---|
| Windows | build/windows/x64/runner/Release/ |
| macOS | build/macos/Build/Products/Release/ |
| Linux | build/linux/x64/release/bundle/ |
6. 공통 데스크톱 기능 구현
창 관리
dependencies: window_manager: ^0.3.0import 'package:window_manager/window_manager.dart';
Future<void> setupWindow() async { await windowManager.ensureInitialized();
WindowOptions windowOptions = WindowOptions( size: Size(1200, 800), minimumSize: Size(800, 600), center: true, backgroundColor: Colors.transparent, title: 'My Desktop App', titleBarStyle: TitleBarStyle.hidden, // 커스텀 타이틀바 );
await windowManager.waitUntilReadyToShow(windowOptions, () async { await windowManager.show(); await windowManager.focus(); });}시스템 트레이
dependencies: system_tray: ^2.0.0import 'package:system_tray/system_tray.dart';
Future<void> initSystemTray() async { final systemTray = SystemTray();
await systemTray.initSystemTray( title: 'My App', iconPath: 'assets/app_icon.png', );
final menu = Menu(); await menu.buildFrom([ MenuItemLabel(label: '열기', onClicked: (menuItem) => showWindow()), MenuSeparator(), MenuItemLabel(label: '종료', onClicked: (menuItem) => exitApp()), ]);
await systemTray.setContextMenu(menu);
systemTray.registerSystemTrayEventHandler((eventName) { if (eventName == kSystemTrayEventClick) { systemTray.popUpContextMenu(); } });}파일 시스템 접근
import 'package:file_selector/file_selector.dart';import 'package:path_provider/path_provider.dart';
Future<void> openFile() async { const XTypeGroup typeGroup = XTypeGroup( label: 'images', extensions: ['jpg', 'png', 'gif'], );
final XFile? file = await openFile(acceptedTypeGroups: [typeGroup]);
if (file != null) { print('선택된 파일: ${file.path}'); }}
Future<void> saveFile() async { final directory = await getApplicationDocumentsDirectory(); final path = '${directory.path}/my_file.txt'; // 파일 저장 로직}단축키 처리
class ShortcutExample extends StatelessWidget { @override Widget build(BuildContext context) { return Shortcuts( shortcuts: { LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyS): SaveIntent(), LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyO): OpenIntent(), LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyQ): QuitIntent(), }, child: Actions( actions: { SaveIntent: CallbackAction<SaveIntent>( onInvoke: (intent) => saveDocument(), ), OpenIntent: CallbackAction<OpenIntent>( onInvoke: (intent) => openDocument(), ), QuitIntent: CallbackAction<QuitIntent>( onInvoke: (intent) => quitApp(), ), }, child: Focus( autofocus: true, child: MyApp(), ), ), ); }}
class SaveIntent extends Intent {}class OpenIntent extends Intent {}class QuitIntent extends Intent {}7. 종합 예제: 크로스 플랫폼 데스크톱 앱
import 'package:flutter/material.dart';import 'package:flutter/foundation.dart';import 'dart:io';
void main() async { WidgetsFlutterBinding.ensureInitialized();
// 플랫폼별 초기화 if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { // 데스크톱 전용 초기화 (window_manager 등) }
runApp(const DesktopApp());}
class DesktopApp extends StatelessWidget { const DesktopApp({super.key});
@override Widget build(BuildContext context) { return MaterialApp( title: 'Cross-Platform Desktop App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), useMaterial3: true, ), home: const HomePage(), ); }}
class HomePage extends StatelessWidget { const HomePage({super.key});
String get platformName { if (Platform.isWindows) return 'Windows'; if (Platform.isMacOS) return 'macOS'; if (Platform.isLinux) return 'Linux'; return 'Unknown'; }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('데스크톱 앱'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( _getPlatformIcon(), size: 100, color: Theme.of(context).primaryColor, ), const SizedBox(height: 20), Text( '$platformName에서 실행 중', style: Theme.of(context).textTheme.headlineMedium, ), const SizedBox(height: 40), ElevatedButton.icon( icon: const Icon(Icons.folder_open), label: const Text('파일 열기'), onPressed: () => _openFile(context), ), ], ), ), ); }
IconData _getPlatformIcon() { if (Platform.isWindows) return Icons.desktop_windows; if (Platform.isMacOS) return Icons.desktop_mac; if (Platform.isLinux) return Icons.computer; return Icons.device_unknown; }
void _openFile(BuildContext context) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('파일 선택 대화상자를 열었습니다')), ); }}마무리
이번 튜토리얼에서는 Flutter로 데스크톱 앱을 개발하는 방법을 살펴보았습니다.
핵심 요약
| 플랫폼 | 핵심 패키지 | 주요 설정 |
|---|---|---|
| Windows | win32, fluent_ui | Visual Studio, CMakeLists.txt |
| macOS | macos_ui | Entitlements, App Sandbox |
| Linux | desktop_notifications | GTK 의존성, .so 라이브러리 |
다음 단계
- 62편에서는 배포 준비와 코드 난독화를 다룹니다.
- 각 플랫폼별 디자인 가이드라인을 학습해보세요.
- window_manager, system_tray 등 데스크톱 특화 패키지를 활용해보세요.
참고 자료
Footnotes
공유
이 글이 도움이 되었다면 다른 사람과 공유해주세요!
Flutter 튜토리얼 61편: 데스크톱 앱 개발
https://moodturnpost.net/posts/flutter/flutter-platform-desktop/ 작성자
Moodturn
게시일
2026-01-08